(61d00a474) v0.9.7.1
This commit is contained in:
514
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Device/GADevice.cs
Normal file
514
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Device/GADevice.cs
Normal file
@@ -0,0 +1,514 @@
|
||||
using System;
|
||||
#if UNITY
|
||||
using UnityEngine;
|
||||
using GameAnalyticsSDK.Net.Store;
|
||||
#endif
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using GameAnalyticsSDK.Net.Logging;
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using Windows.System.Profile;
|
||||
using Windows.Security.ExchangeActiveSyncProvisioning;
|
||||
using Windows.Storage;
|
||||
using Windows.System.UserProfile;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Windows.Storage.Streams;
|
||||
#endif
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Device
|
||||
{
|
||||
internal static class GADevice
|
||||
{
|
||||
#if WINDOWS_UWP
|
||||
private const string _sdkWrapperVersion = "uwp 2.1.7";
|
||||
#elif WINDOWS_WSA
|
||||
private const string _sdkWrapperVersion = "wsa 2.1.7";
|
||||
#else
|
||||
private const string _sdkWrapperVersion = "mono 2.1.7";
|
||||
#endif
|
||||
#if UNITY
|
||||
private static readonly string _buildPlatform = UnityRuntimePlatformToString(Application.platform);
|
||||
private static readonly string _deviceModel = SystemInfo.deviceType.ToString().ToLowerInvariant();
|
||||
private static string _writablepath = GAStore.InMemory ? "" : GetPersistentPath();
|
||||
#else
|
||||
private static readonly string _buildPlatform = RuntimePlatformToString();
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
private static readonly string _deviceModel = GetDeviceModel();
|
||||
private static readonly string _advertisingId = AdvertisingManager.AdvertisingId;
|
||||
private static string _deviceId = GetDeviceId();
|
||||
#else
|
||||
private static readonly string _deviceModel = "unknown";
|
||||
#endif
|
||||
private static string _writablepath = GetPersistentPath();
|
||||
#endif
|
||||
private static readonly string _osVersion = GetOSVersionString();
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
private static readonly string _deviceManufacturer = GetDeviceManufacturer();
|
||||
#else
|
||||
private static readonly string _deviceManufacturer = "unknown";
|
||||
#endif
|
||||
|
||||
public static void Touch()
|
||||
{
|
||||
}
|
||||
|
||||
public static string SdkGameEngineVersion
|
||||
{
|
||||
private get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static string GameEngineVersion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static string ConnectionType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static string RelevantSdkVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!string.IsNullOrEmpty(SdkGameEngineVersion))
|
||||
{
|
||||
return SdkGameEngineVersion;
|
||||
}
|
||||
return _sdkWrapperVersion;
|
||||
}
|
||||
}
|
||||
|
||||
public static string BuildPlatform
|
||||
{
|
||||
get
|
||||
{
|
||||
return _buildPlatform;
|
||||
}
|
||||
}
|
||||
|
||||
public static string OSVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return _osVersion;
|
||||
}
|
||||
}
|
||||
|
||||
public static string DeviceModel
|
||||
{
|
||||
get
|
||||
{
|
||||
return _deviceModel;
|
||||
}
|
||||
}
|
||||
|
||||
public static string DeviceManufacturer
|
||||
{
|
||||
get
|
||||
{
|
||||
return _deviceManufacturer;
|
||||
}
|
||||
}
|
||||
|
||||
public static string WritablePath
|
||||
{
|
||||
get
|
||||
{
|
||||
return _writablepath;
|
||||
}
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP
|
||||
public static string AdvertisingId
|
||||
{
|
||||
get
|
||||
{
|
||||
return _advertisingId;
|
||||
}
|
||||
}
|
||||
|
||||
public static string DeviceId
|
||||
{
|
||||
get
|
||||
{
|
||||
return _deviceId;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY
|
||||
public static void UpdateConnectionType()
|
||||
{
|
||||
switch(Application.internetReachability)
|
||||
{
|
||||
case NetworkReachability.ReachableViaCarrierDataNetwork:
|
||||
{
|
||||
ConnectionType = "wwan";
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkReachability.ReachableViaLocalAreaNetwork:
|
||||
{
|
||||
ConnectionType = "lan";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
ConnectionType = "offline";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetOSVersionString()
|
||||
{
|
||||
string osVersion = SystemInfo.operatingSystem;
|
||||
|
||||
GALogger.D("GetOSVersionString: " + osVersion);
|
||||
|
||||
// Capture and process OS version information
|
||||
// For Windows
|
||||
Match regexResult = Regex.Match(osVersion, @"Windows.*?\((\d{0,5}\.\d{0,5}\.(\d{0,5}))\)");
|
||||
if(regexResult.Success)
|
||||
{
|
||||
string versionNumberString = regexResult.Groups[1].Value;
|
||||
string buildNumberString = regexResult.Groups[2].Value;
|
||||
// Fix a bug in older versions of Unity where Windows 10 isn't recognised properly
|
||||
int buildNumber = 0;
|
||||
Int32.TryParse(buildNumberString, out buildNumber);
|
||||
if(buildNumber > 10000)
|
||||
{
|
||||
versionNumberString = "10.0." + buildNumberString;
|
||||
}
|
||||
return "windows " + versionNumberString;
|
||||
}
|
||||
// For OS X
|
||||
regexResult = Regex.Match(osVersion, @"Mac OS X (\d{0,5}\.\d{0,5}\.\d{0,5})");
|
||||
if(regexResult.Success)
|
||||
{
|
||||
return "mac_osx " + regexResult.Captures[0].Value.Replace("Mac OS X ", "");
|
||||
}
|
||||
regexResult = Regex.Match(osVersion, @"Mac OS X (\d{0,5}_\d{0,5}_\d{0,5})");
|
||||
if(regexResult.Success)
|
||||
{
|
||||
return "mac_osx " + regexResult.Captures[0].Value.Replace("Mac OS X ", "").Replace("_", ".");
|
||||
}
|
||||
// Not supporting other OS yet. The default version string won't be accepted by GameAnalytics
|
||||
return UnityRuntimePlatformToString(Application.platform) + " 0.0.0";
|
||||
}
|
||||
|
||||
private static string GetPersistentPath()
|
||||
{
|
||||
string result = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + Path.DirectorySeparatorChar + "GameAnalytics" + Path.DirectorySeparatorChar + System.AppDomain.CurrentDomain.FriendlyName;
|
||||
|
||||
if (!Directory.Exists(result))
|
||||
{
|
||||
Directory.CreateDirectory(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string UnityRuntimePlatformToString(RuntimePlatform platform)
|
||||
{
|
||||
switch(platform)
|
||||
{
|
||||
case RuntimePlatform.LinuxPlayer:
|
||||
{
|
||||
return "linux";
|
||||
}
|
||||
|
||||
case RuntimePlatform.OSXPlayer:
|
||||
case RuntimePlatform.OSXDashboardPlayer:
|
||||
{
|
||||
return "mac_osx";
|
||||
}
|
||||
|
||||
case RuntimePlatform.PS3:
|
||||
{
|
||||
return "ps3";
|
||||
}
|
||||
|
||||
case RuntimePlatform.PS4:
|
||||
{
|
||||
return "ps4";
|
||||
}
|
||||
|
||||
case RuntimePlatform.PSP2:
|
||||
{
|
||||
return "vita";
|
||||
}
|
||||
|
||||
case RuntimePlatform.WindowsPlayer:
|
||||
{
|
||||
return "windows";
|
||||
}
|
||||
|
||||
#if UNITY_5
|
||||
case RuntimePlatform.PSM:
|
||||
{
|
||||
return "psm";
|
||||
}
|
||||
|
||||
case RuntimePlatform.WiiU:
|
||||
{
|
||||
return "wiiu";
|
||||
}
|
||||
|
||||
case RuntimePlatform.WebGLPlayer:
|
||||
{
|
||||
return "webgl";
|
||||
}
|
||||
|
||||
case RuntimePlatform.WSAPlayerARM:
|
||||
case RuntimePlatform.WSAPlayerX64:
|
||||
case RuntimePlatform.WSAPlayerX86:
|
||||
{
|
||||
switch(SystemInfo.deviceType)
|
||||
{
|
||||
case DeviceType.Desktop:
|
||||
{
|
||||
return "uwp_desktop";
|
||||
}
|
||||
|
||||
case DeviceType.Handheld:
|
||||
{
|
||||
return "uwp_mobile";
|
||||
}
|
||||
|
||||
case DeviceType.Console:
|
||||
{
|
||||
return "uwp_console";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return "uwp_desktop";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
case RuntimePlatform.WP8Player:
|
||||
{
|
||||
return "windows_phone";
|
||||
}
|
||||
|
||||
case RuntimePlatform.XBOX360:
|
||||
{
|
||||
return "xbox360";
|
||||
}
|
||||
|
||||
case RuntimePlatform.XboxOne:
|
||||
{
|
||||
return "xboxone";
|
||||
}
|
||||
|
||||
case RuntimePlatform.TizenPlayer:
|
||||
{
|
||||
return "tizen";
|
||||
}
|
||||
|
||||
case RuntimePlatform.SamsungTVPlayer:
|
||||
{
|
||||
return "samsung_tv";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
public static void UpdateConnectionType()
|
||||
{
|
||||
ConnectionType = "lan";
|
||||
}
|
||||
|
||||
private static string GetOSVersionString()
|
||||
{
|
||||
#if WINDOWS_UWP
|
||||
string deviceFamilyVersion = AnalyticsInfo.VersionInfo.DeviceFamilyVersion;
|
||||
ulong version = ulong.Parse(deviceFamilyVersion);
|
||||
ulong major = (version & 0xFFFF000000000000L) >> 48;
|
||||
ulong minor = (version & 0x0000FFFF00000000L) >> 32;
|
||||
ulong build = (version & 0x00000000FFFF0000L) >> 16;
|
||||
return BuildPlatform + string.Format(" {0}.{1}.{2}", major, minor, build);
|
||||
#elif WINDOWS_WSA
|
||||
// Always 8.1 on Universal Windows 8.1
|
||||
return BuildPlatform + " 8";
|
||||
#else
|
||||
Version v = Environment.OSVersion.Version;
|
||||
return BuildPlatform + string.Format(" {0}.{1}.{2}", v.Major, v.Minor, v.Build);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
private static string GetDeviceId()
|
||||
{
|
||||
string result = "";
|
||||
|
||||
#if WINDOWS_UWP
|
||||
if(ApiInformation.IsTypePresent("Windows.System.Profile.HardwareIdentification"))
|
||||
#endif
|
||||
{
|
||||
var token = HardwareIdentification.GetPackageSpecificToken(null);
|
||||
var hardwareId = token.Id;
|
||||
var dataReader = DataReader.FromBuffer(hardwareId);
|
||||
|
||||
byte[] bytes = new byte[hardwareId.Length];
|
||||
dataReader.ReadBytes(bytes);
|
||||
|
||||
result = BitConverter.ToString(bytes).Replace("-", "");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetDeviceManufacturer()
|
||||
{
|
||||
EasClientDeviceInformation eas = new EasClientDeviceInformation();
|
||||
return eas.SystemManufacturer;
|
||||
}
|
||||
|
||||
private static string GetDeviceModel()
|
||||
{
|
||||
EasClientDeviceInformation eas = new EasClientDeviceInformation();
|
||||
return eas.SystemProductName;
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP
|
||||
private static string RuntimePlatformToString()
|
||||
{
|
||||
switch(AnalyticsInfo.VersionInfo.DeviceFamily)
|
||||
{
|
||||
case "Windows.Mobile":
|
||||
{
|
||||
return "uwp_mobile";
|
||||
}
|
||||
|
||||
case "Windows.Desktop":
|
||||
{
|
||||
return "uwp_desktop";
|
||||
}
|
||||
|
||||
case "Windows.Universal":
|
||||
{
|
||||
return "uwp_iot";
|
||||
}
|
||||
|
||||
case "Windows.Xbox":
|
||||
{
|
||||
return "uwp_console";
|
||||
}
|
||||
|
||||
case "Windows.Team":
|
||||
{
|
||||
return "uwp_surfacehub";
|
||||
}
|
||||
|
||||
case "Windows.Holographic":
|
||||
{
|
||||
return "uwp_holographic";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return AnalyticsInfo.VersionInfo.DeviceFamily;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
private static string RuntimePlatformToString()
|
||||
{
|
||||
EasClientDeviceInformation eas = new EasClientDeviceInformation();
|
||||
switch(eas.OperatingSystem.ToUpperInvariant())
|
||||
{
|
||||
case "WINDOWSPHONE":
|
||||
{
|
||||
return "windows_phone";
|
||||
}
|
||||
|
||||
case "WINDOWS":
|
||||
{
|
||||
return "windows";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return eas.OperatingSystem;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
private static string RuntimePlatformToString()
|
||||
{
|
||||
switch(Environment.OSVersion.Platform)
|
||||
{
|
||||
case PlatformID.Unix:
|
||||
{
|
||||
// Well, there are chances MacOSX is reported as Unix instead of MacOSX.
|
||||
// Instead of platform check, we'll do a feature checks (Mac specific root folders)
|
||||
if(Directory.Exists("/Applications") && Directory.Exists("/System") && Directory.Exists("/Users") && Directory.Exists("/Volumes"))
|
||||
{
|
||||
return "mac_osx";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "linux";
|
||||
}
|
||||
}
|
||||
|
||||
case PlatformID.MacOSX:
|
||||
{
|
||||
return "mac_osx";
|
||||
}
|
||||
|
||||
case PlatformID.Win32NT:
|
||||
case PlatformID.Win32S:
|
||||
case PlatformID.Win32Windows:
|
||||
case PlatformID.WinCE:
|
||||
{
|
||||
return "windows";
|
||||
}
|
||||
|
||||
case PlatformID.Xbox:
|
||||
{
|
||||
return "xbox360";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static string GetPersistentPath()
|
||||
{
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
System.Threading.Tasks.Task<StorageFolder> gaFolderTask = System.Threading.Tasks.Task.Run<StorageFolder>(async() => await ApplicationData.Current.LocalFolder.CreateFolderAsync("GameAnalytics", CreationCollisionOption.OpenIfExists));
|
||||
return gaFolderTask.GetAwaiter().GetResult().Path;
|
||||
#else
|
||||
string result = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + Path.DirectorySeparatorChar + "GameAnalytics" + Path.DirectorySeparatorChar + System.AppDomain.CurrentDomain.FriendlyName;
|
||||
|
||||
if(!Directory.Exists(result))
|
||||
{
|
||||
Directory.CreateDirectory(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GameAnalyticsSDK.Net
|
||||
{
|
||||
public enum EGAErrorSeverity
|
||||
{
|
||||
Undefined = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warning = 3,
|
||||
Error = 4,
|
||||
Critical = 5
|
||||
}
|
||||
}
|
||||
|
||||
10
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/EGAGender.cs
Normal file
10
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/EGAGender.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace GameAnalyticsSDK.Net
|
||||
{
|
||||
public enum EGAGender
|
||||
{
|
||||
Undefined = 0,
|
||||
Male = 1,
|
||||
Female = 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace GameAnalyticsSDK.Net
|
||||
{
|
||||
public enum EGALoggerMessageType
|
||||
{
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
Debug
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace GameAnalyticsSDK.Net
|
||||
{
|
||||
public enum EGAProgressionStatus
|
||||
{
|
||||
Undefined = 0,
|
||||
Start = 1,
|
||||
Complete = 2,
|
||||
Fail = 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GameAnalyticsSDK.Net
|
||||
{
|
||||
public enum EGAResourceFlowType
|
||||
{
|
||||
Undefined = 0,
|
||||
Source = 1,
|
||||
Sink = 2
|
||||
}
|
||||
}
|
||||
|
||||
853
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Events/GAEvents.cs
Normal file
853
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Events/GAEvents.cs
Normal file
@@ -0,0 +1,853 @@
|
||||
using System;
|
||||
using GameAnalyticsSDK.Net.Threading;
|
||||
using GameAnalyticsSDK.Net.Logging;
|
||||
using System.Collections.Generic;
|
||||
using GameAnalyticsSDK.Net.Store;
|
||||
using GameAnalyticsSDK.Net.Utilities;
|
||||
using GameAnalyticsSDK.Net.Http;
|
||||
using GameAnalyticsSDK.Net.State;
|
||||
using GameAnalyticsSDK.Net.Validators;
|
||||
using System.Globalization;
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Events
|
||||
{
|
||||
internal class GAEvents
|
||||
{
|
||||
#region Fields and properties
|
||||
|
||||
private static readonly GAEvents _instance = new GAEvents();
|
||||
private const string CategorySessionStart = "user";
|
||||
private const string CategorySessionEnd = "session_end";
|
||||
private const string CategoryDesign = "design";
|
||||
private const string CategoryBusiness = "business";
|
||||
private const string CategoryProgression = "progression";
|
||||
private const string CategoryResource = "resource";
|
||||
private const string CategoryError = "error";
|
||||
private bool isRunning;
|
||||
private bool keepRunning;
|
||||
private const double ProcessEventsIntervalInSeconds = 8.0;
|
||||
private const int MaxEventCount = 500;
|
||||
|
||||
private static GAEvents Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // Fields and properties
|
||||
|
||||
private GAEvents()
|
||||
{
|
||||
}
|
||||
|
||||
#region Public methods
|
||||
|
||||
public static void StopEventQueue()
|
||||
{
|
||||
Instance.keepRunning = false;
|
||||
}
|
||||
|
||||
public static void EnsureEventQueueIsRunning()
|
||||
{
|
||||
Instance.keepRunning = true;
|
||||
|
||||
if(!Instance.isRunning)
|
||||
{
|
||||
Instance.isRunning = true;
|
||||
GAThreading.ScheduleTimer(ProcessEventsIntervalInSeconds, "processEventQueue", ProcessEventQueue);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddSessionStartEvent()
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string categorySessionStart = CategorySessionStart;
|
||||
|
||||
// Event specific data
|
||||
JSONObject eventDict = new JSONObject();
|
||||
eventDict["category"] = categorySessionStart;
|
||||
|
||||
// Increment session number and persist
|
||||
GAState.IncrementSessionNum();
|
||||
GAStore.SetState(GAState.SessionNumKey, GAState.SessionNum.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
// Add custom dimensions
|
||||
AddDimensionsToEvent(eventDict);
|
||||
|
||||
// Add to store
|
||||
AddEventToStore(eventDict);
|
||||
|
||||
// Log
|
||||
GALogger.I("Add SESSION START event");
|
||||
|
||||
// Send event right away
|
||||
ProcessEvents(categorySessionStart, false);
|
||||
}
|
||||
|
||||
public static void AddSessionEndEvent()
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long session_start_ts = GAState.SessionStart;
|
||||
long client_ts_adjusted = GAState.GetClientTsAdjusted();
|
||||
long sessionLength = client_ts_adjusted - session_start_ts;
|
||||
|
||||
if(sessionLength < 0)
|
||||
{
|
||||
// Should never happen.
|
||||
// Could be because of edge cases regarding time altering on device.
|
||||
GALogger.W("Session length was calculated to be less then 0. Should not be possible. Resetting to 0.");
|
||||
sessionLength = 0;
|
||||
}
|
||||
|
||||
// Event specific data
|
||||
JSONObject eventDict = new JSONObject();
|
||||
eventDict["category"] = CategorySessionEnd;
|
||||
eventDict.Add("length", new JSONNumber(sessionLength));
|
||||
|
||||
// Add custom dimensions
|
||||
AddDimensionsToEvent(eventDict);
|
||||
|
||||
// Add to store
|
||||
AddEventToStore(eventDict);
|
||||
|
||||
// Log
|
||||
GALogger.I("Add SESSION END event.");
|
||||
|
||||
// Send all event right away
|
||||
ProcessEvents("", false);
|
||||
}
|
||||
|
||||
public static void AddBusinessEvent(
|
||||
string currency,
|
||||
int amount,
|
||||
string itemType,
|
||||
string itemId,
|
||||
string cartType,
|
||||
IDictionary<string, object> fields
|
||||
)
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate event params
|
||||
if (!GAValidator.ValidateBusinessEvent(currency, amount, cartType, itemType, itemId))
|
||||
{
|
||||
//GAHTTPApi.Instance.SendSdkErrorEvent(EGASdkErrorType.Rejected);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create empty eventData
|
||||
JSONObject eventDict = new JSONObject();
|
||||
|
||||
// Increment transaction number and persist
|
||||
GAState.IncrementTransactionNum();
|
||||
GAStore.SetState(GAState.TransactionNumKey, GAState.TransactionNum.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
// Required
|
||||
eventDict["event_id"] = itemType + ":" + itemId;
|
||||
eventDict["category"] = CategoryBusiness;
|
||||
eventDict["currency"] = currency;
|
||||
eventDict.Add("amount", new JSONNumber(amount));
|
||||
eventDict.Add(GAState.TransactionNumKey, new JSONNumber(GAState.TransactionNum));
|
||||
|
||||
// Optional
|
||||
if (!string.IsNullOrEmpty(cartType))
|
||||
{
|
||||
eventDict.Add("cart_type", cartType);
|
||||
}
|
||||
|
||||
// Add custom dimensions
|
||||
AddDimensionsToEvent(eventDict);
|
||||
|
||||
// Add custom fields
|
||||
AddFieldsToEvent(eventDict, GAState.ValidateAndCleanCustomFields(fields));
|
||||
|
||||
// Log
|
||||
GALogger.I("Add BUSINESS event: {currency:" + currency + ", amount:" + amount + ", itemType:" + itemType + ", itemId:" + itemId + ", cartType:" + cartType + "}");
|
||||
|
||||
// Send to store
|
||||
AddEventToStore(eventDict);
|
||||
}
|
||||
|
||||
public static void AddResourceEvent(EGAResourceFlowType flowType, string currency, double amount, string itemType, string itemId, IDictionary<string, object> fields)
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate event params
|
||||
if (!GAValidator.ValidateResourceEvent(flowType, currency, (long)amount, itemType, itemId))
|
||||
{
|
||||
//GAHTTPApi.Instance.SendSdkErrorEvent(EGASdkErrorType.Rejected);
|
||||
return;
|
||||
}
|
||||
|
||||
// If flow type is sink reverse amount
|
||||
if (flowType == EGAResourceFlowType.Sink)
|
||||
{
|
||||
amount *= -1;
|
||||
}
|
||||
|
||||
// Create empty eventData
|
||||
JSONObject eventDict = new JSONObject();
|
||||
|
||||
// insert event specific values
|
||||
string flowTypeString = ResourceFlowTypeToString(flowType);
|
||||
eventDict["event_id"] = flowTypeString + ":" + currency + ":" + itemType + ":" + itemId;
|
||||
eventDict["category"] = CategoryResource;
|
||||
eventDict.Add("amount", new JSONNumber(amount));
|
||||
|
||||
// Add custom dimensions
|
||||
AddDimensionsToEvent(eventDict);
|
||||
|
||||
// Add custom fields
|
||||
AddFieldsToEvent(eventDict, GAState.ValidateAndCleanCustomFields(fields));
|
||||
|
||||
// Log
|
||||
GALogger.I("Add RESOURCE event: {currency:" + currency + ", amount:" + amount + ", itemType:" + itemType + ", itemId:" + itemId + "}");
|
||||
|
||||
// Send to store
|
||||
AddEventToStore(eventDict);
|
||||
}
|
||||
|
||||
public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01, string progression02, string progression03, double score, bool sendScore, IDictionary<string, object> fields)
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string progressionStatusString = ProgressionStatusToString(progressionStatus);
|
||||
|
||||
// Validate event params
|
||||
if (!GAValidator.ValidateProgressionEvent(progressionStatus, progression01, progression02, progression03))
|
||||
{
|
||||
//GAHTTPApi.Instance.SendSdkErrorEvent(EGASdkErrorType.Rejected);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create empty eventData
|
||||
JSONObject eventDict = new JSONObject();
|
||||
|
||||
// Progression identifier
|
||||
string progressionIdentifier;
|
||||
|
||||
if (string.IsNullOrEmpty(progression02))
|
||||
{
|
||||
progressionIdentifier = progression01;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(progression03))
|
||||
{
|
||||
progressionIdentifier = progression01 + ":" + progression02;
|
||||
}
|
||||
else
|
||||
{
|
||||
progressionIdentifier = progression01 + ":" + progression02 + ":" + progression03;
|
||||
}
|
||||
|
||||
// Append event specifics
|
||||
eventDict["category"] = CategoryProgression;
|
||||
eventDict["event_id"] = progressionStatusString + ":" + progressionIdentifier;
|
||||
|
||||
// Attempt
|
||||
double attempt_num = 0;
|
||||
|
||||
// Add score if specified and status is not start
|
||||
if (sendScore && progressionStatus != EGAProgressionStatus.Start)
|
||||
{
|
||||
eventDict.Add("score", new JSONNumber(score));
|
||||
}
|
||||
|
||||
// Count attempts on each progression fail and persist
|
||||
if (progressionStatus == EGAProgressionStatus.Fail)
|
||||
{
|
||||
// Increment attempt number
|
||||
GAState.IncrementProgressionTries(progressionIdentifier);
|
||||
}
|
||||
|
||||
// increment and add attempt_num on complete and delete persisted
|
||||
if (progressionStatus == EGAProgressionStatus.Complete)
|
||||
{
|
||||
// Increment attempt number
|
||||
GAState.IncrementProgressionTries(progressionIdentifier);
|
||||
|
||||
// Add to event
|
||||
attempt_num = GAState.GetProgressionTries(progressionIdentifier);
|
||||
eventDict.Add("attempt_num", new JSONNumber(attempt_num));
|
||||
|
||||
// Clear
|
||||
GAState.ClearProgressionTries(progressionIdentifier);
|
||||
}
|
||||
|
||||
// Add custom dimensions
|
||||
AddDimensionsToEvent(eventDict);
|
||||
|
||||
// Add custom fields
|
||||
AddFieldsToEvent(eventDict, GAState.ValidateAndCleanCustomFields(fields));
|
||||
|
||||
// Log
|
||||
GALogger.I("Add PROGRESSION event: {status:" + progressionStatusString + ", progression01:" + progression01 + ", progression02:" + progression02 + ", progression03:" + progression03 + ", score:" + score + ", attempt:" + attempt_num + "}");
|
||||
|
||||
// Send to store
|
||||
AddEventToStore(eventDict);
|
||||
}
|
||||
|
||||
public static void AddDesignEvent(string eventId, double value, bool sendValue, IDictionary<string, object> fields)
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate
|
||||
if (!GAValidator.ValidateDesignEvent(eventId, value))
|
||||
{
|
||||
//GAHTTPApi.Instance.SendSdkErrorEvent(EGASdkErrorType.Rejected);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create empty eventData
|
||||
JSONObject eventData = new JSONObject();
|
||||
|
||||
// Append event specifics
|
||||
eventData["category"] = CategoryDesign;
|
||||
eventData["event_id"] = eventId;
|
||||
|
||||
if(sendValue)
|
||||
{
|
||||
eventData.Add("value", new JSONNumber(value));
|
||||
}
|
||||
|
||||
// Add custom dimensions
|
||||
AddDimensionsToEvent(eventData);
|
||||
|
||||
// Add custom fields
|
||||
AddFieldsToEvent(eventData, GAState.ValidateAndCleanCustomFields(fields));
|
||||
|
||||
// Log
|
||||
GALogger.I("Add DESIGN event: {eventId:" + eventId + ", value:" + value + "}");
|
||||
|
||||
// Send to store
|
||||
AddEventToStore(eventData);
|
||||
}
|
||||
|
||||
public static void AddErrorEvent(EGAErrorSeverity severity, string message, IDictionary<string, object> fields)
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string severityString = ErrorSeverityToString(severity);
|
||||
|
||||
// Validate
|
||||
if (!GAValidator.ValidateErrorEvent(severity, message))
|
||||
{
|
||||
//GAHTTPApi.Instance.SendSdkErrorEvent(EGASdkErrorType.Rejected);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create empty eventData
|
||||
JSONObject eventData = new JSONObject();
|
||||
|
||||
// Append event specifics
|
||||
eventData["category"] = CategoryError;
|
||||
eventData["severity"] = severityString;
|
||||
eventData["message"] = message;
|
||||
|
||||
// Add custom dimensions
|
||||
AddDimensionsToEvent(eventData);
|
||||
|
||||
// Add custom fields
|
||||
AddFieldsToEvent(eventData, GAState.ValidateAndCleanCustomFields(fields));
|
||||
|
||||
// Log
|
||||
GALogger.I("Add ERROR event: {severity:" + severityString + ", message:" + message + "}");
|
||||
|
||||
// Send to store
|
||||
AddEventToStore(eventData);
|
||||
}
|
||||
|
||||
#endregion // Public methods
|
||||
|
||||
#region Private methods
|
||||
|
||||
private static void ProcessEventQueue()
|
||||
{
|
||||
ProcessEvents("", true);
|
||||
if(Instance.keepRunning)
|
||||
{
|
||||
GAThreading.ScheduleTimer(ProcessEventsIntervalInSeconds, "processEventQueue", ProcessEventQueue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance.isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
private async static void ProcessEvents(string category, bool performCleanUp)
|
||||
#else
|
||||
private static void ProcessEvents(string category, bool performCleanUp)
|
||||
#endif
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string requestIdentifier = Guid.NewGuid().ToString();
|
||||
|
||||
string selectSql;
|
||||
string updateSql;
|
||||
string deleteSql = "DELETE FROM ga_events WHERE status = '" + requestIdentifier + "'";
|
||||
string putbackSql = "UPDATE ga_events SET status = 'new' WHERE status = '" + requestIdentifier + "';";
|
||||
|
||||
// Cleanup
|
||||
if(performCleanUp)
|
||||
{
|
||||
CleanupEvents();
|
||||
FixMissingSessionEndEvents();
|
||||
}
|
||||
|
||||
// Prepare SQL
|
||||
string andCategory = "";
|
||||
if(!string.IsNullOrEmpty(category))
|
||||
{
|
||||
andCategory = " AND category='" + category + "' ";
|
||||
}
|
||||
selectSql = "SELECT event FROM ga_events WHERE status = 'new' " + andCategory + ";";
|
||||
updateSql = "UPDATE ga_events SET status = '" + requestIdentifier + "' WHERE status = 'new' " + andCategory + ";";
|
||||
|
||||
// Get events to process
|
||||
JSONArray events = GAStore.ExecuteQuerySync(selectSql);
|
||||
|
||||
// Check for errors or empty
|
||||
if(events == null || events.Count == 0)
|
||||
{
|
||||
GALogger.I("Event queue: No events to send");
|
||||
UpdateSessionTime();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check number of events and take some action if there are too many?
|
||||
if(events.Count > MaxEventCount)
|
||||
{
|
||||
// Make a limit request
|
||||
selectSql = "SELECT client_ts FROM ga_events WHERE status = 'new' " + andCategory + " ORDER BY client_ts ASC LIMIT 0," + MaxEventCount + ";";
|
||||
events = GAStore.ExecuteQuerySync(selectSql);
|
||||
if(events == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get last timestamp
|
||||
JSONNode lastItem = events[events.Count - 1];
|
||||
string lastTimestamp = lastItem["client_ts"].Value;
|
||||
|
||||
// Select again
|
||||
selectSql = "SELECT event FROM ga_events WHERE status = 'new' " + andCategory + " AND client_ts<='" + lastTimestamp + "';";
|
||||
events = GAStore.ExecuteQuerySync(selectSql);
|
||||
if (events == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update sql
|
||||
updateSql = "UPDATE ga_events SET status='" + requestIdentifier + "' WHERE status='new' " + andCategory + " AND client_ts<='" + lastTimestamp + "';";
|
||||
}
|
||||
|
||||
// Log
|
||||
GALogger.I("Event queue: Sending " + events.Count + " events.");
|
||||
|
||||
// Set status of events to 'sending' (also check for error)
|
||||
if (GAStore.ExecuteQuerySync(updateSql) == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Create payload data from events
|
||||
List<JSONNode> payloadArray = new List<JSONNode>();
|
||||
|
||||
for(int i = 0; i < events.Count; ++i)
|
||||
{
|
||||
JSONNode ev = events[i];
|
||||
JSONNode eventDict = null;
|
||||
|
||||
try
|
||||
{
|
||||
eventDict = JSONNode.LoadFromBinaryBase64(ev["event"].Value);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
//GALogger.E("ProcessEvents: Error decoding json, " + e);
|
||||
}
|
||||
|
||||
if (eventDict != null && eventDict.Count != 0)
|
||||
{
|
||||
payloadArray.Add(eventDict);
|
||||
}
|
||||
}
|
||||
|
||||
// send events
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
KeyValuePair<EGAHTTPApiResponse, JSONNode> result = await GAHTTPApi.Instance.SendEventsInArray(payloadArray);
|
||||
#else
|
||||
KeyValuePair<EGAHTTPApiResponse, JSONNode> result = GAHTTPApi.Instance.SendEventsInArray(payloadArray);
|
||||
#endif
|
||||
|
||||
ProcessEvents(result.Key, result.Value, putbackSql, deleteSql, payloadArray.Count);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GALogger.E("Error during ProcessEvents(): " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ProcessEvents(EGAHTTPApiResponse responseEnum, JSONNode dataDict, string putbackSql, string deleteSql, int eventCount)
|
||||
{
|
||||
if(responseEnum == EGAHTTPApiResponse.Ok)
|
||||
{
|
||||
// Delete events
|
||||
GAStore.ExecuteQuerySync(deleteSql);
|
||||
GALogger.I("Event queue: " + eventCount + " events sent.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Put events back (Only in case of no response)
|
||||
if(responseEnum == EGAHTTPApiResponse.NoResponse)
|
||||
{
|
||||
GALogger.W("Event queue: Failed to send events to collector - Retrying next time");
|
||||
GAStore.ExecuteQuerySync(putbackSql);
|
||||
// Delete events (When getting some anwser back always assume events are processed)
|
||||
}
|
||||
else
|
||||
{
|
||||
if(dataDict != null)
|
||||
{
|
||||
JSONNode json = null;
|
||||
IEnumerator<JSONNode> enumerator = dataDict.Children.GetEnumerator();
|
||||
if(enumerator.MoveNext())
|
||||
{
|
||||
json = enumerator.Current;
|
||||
}
|
||||
|
||||
if(responseEnum == EGAHTTPApiResponse.BadRequest && json is JSONArray)
|
||||
{
|
||||
GALogger.W("Event queue: " + eventCount + " events sent. " + dataDict.Count + " events failed GA server validation.");
|
||||
}
|
||||
else
|
||||
{
|
||||
GALogger.W("Event queue: Failed to send events.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GALogger.W("Event queue: Failed to send events.");
|
||||
}
|
||||
|
||||
GAStore.ExecuteQuerySync(deleteSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CleanupEvents()
|
||||
{
|
||||
GAStore.ExecuteQuerySync("UPDATE ga_events SET status = 'new';");
|
||||
}
|
||||
|
||||
private static void FixMissingSessionEndEvents()
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all sessions that are not current
|
||||
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
parameters.Add("$session_id", GAState.SessionId);
|
||||
|
||||
|
||||
string sql = "SELECT timestamp, event FROM ga_session WHERE session_id != $session_id;";
|
||||
JSONArray sessions = GAStore.ExecuteQuerySync(sql, parameters);
|
||||
|
||||
if (sessions == null || sessions.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GALogger.I(sessions.Count + " session(s) located with missing session_end event.");
|
||||
|
||||
// Add missing session_end events
|
||||
for (int i = 0; i < sessions.Count; ++i)
|
||||
{
|
||||
JSONNode session = sessions[i];
|
||||
JSONNode sessionEndEvent = null;
|
||||
|
||||
try
|
||||
{
|
||||
sessionEndEvent = JSONNode.LoadFromBinaryBase64(session["event"].Value);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
//GALogger.E("FixMissingSessionEndEvents: Error decoding json, " + e);
|
||||
}
|
||||
|
||||
if(sessionEndEvent != null)
|
||||
{
|
||||
long event_ts = sessionEndEvent["client_ts"].AsLong;
|
||||
long start_ts = session["timestamp"].AsLong;
|
||||
|
||||
long length = event_ts - start_ts;
|
||||
length = Math.Max(0, length);
|
||||
|
||||
GALogger.D("fixMissingSessionEndEvents length calculated: " + length);
|
||||
|
||||
sessionEndEvent["category"] = CategorySessionEnd;
|
||||
sessionEndEvent.Add("length", new JSONNumber(length));
|
||||
|
||||
// Add to store
|
||||
AddEventToStore(sessionEndEvent.AsObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
GALogger.I("Problem decoding session_end event. Skipping this session_end event.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if WINDOWS_WSA
|
||||
private async static void AddEventToStore(JSONObject eventData)
|
||||
#else
|
||||
private static void AddEventToStore(JSONObject eventData)
|
||||
#endif
|
||||
{
|
||||
// Check if datastore is available
|
||||
if (!GAStore.IsTableReady)
|
||||
{
|
||||
GALogger.W("Could not add event: SDK datastore error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are initialized
|
||||
if (!GAState.Initialized)
|
||||
{
|
||||
GALogger.W("Could not add event: SDK is not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Check db size limits (10mb)
|
||||
// If database is too large block all except user, session and business
|
||||
if (GAStore.IsDbTooLargeForEvents && !GAUtilities.StringMatch(eventData["category"].Value, "^(user|session_end|business)$"))
|
||||
{
|
||||
GALogger.W("Database too large. Event has been blocked.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get default annotations
|
||||
JSONObject ev = GAState.GetEventAnnotations();
|
||||
|
||||
// Create json with only default annotations
|
||||
string jsonDefaults = ev.SaveToBinaryBase64();
|
||||
|
||||
// Merge with eventData
|
||||
foreach(KeyValuePair<string,JSONNode> pair in eventData)
|
||||
{
|
||||
ev.Add(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
// Create json string representation
|
||||
string json = ev.ToString();
|
||||
|
||||
// output if VERBOSE LOG enabled
|
||||
|
||||
GALogger.II("Event added to queue: " + json);
|
||||
|
||||
// Add to store
|
||||
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
parameters.Add("$status", "new");
|
||||
parameters.Add("$category", ev["category"].Value);
|
||||
parameters.Add("$session_id", ev["session_id"].Value);
|
||||
parameters.Add("$client_ts", ev["client_ts"].Value);
|
||||
parameters.Add("$event", ev.SaveToBinaryBase64());
|
||||
string sql = "INSERT INTO ga_events (status, category, session_id, client_ts, event) VALUES($status, $category, $session_id, $client_ts, $event);";
|
||||
|
||||
GAStore.ExecuteQuerySync(sql, parameters);
|
||||
|
||||
// Add to session store if not last
|
||||
if (eventData["category"].Value.Equals(CategorySessionEnd))
|
||||
{
|
||||
parameters.Clear();
|
||||
parameters.Add("$session_id", ev["session_id"].Value);
|
||||
sql = "DELETE FROM ga_session WHERE session_id = $session_id;";
|
||||
GAStore.ExecuteQuerySync(sql, parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
sql = "INSERT OR REPLACE INTO ga_session(session_id, timestamp, event) VALUES($session_id, $timestamp, $event);";
|
||||
parameters.Clear();
|
||||
parameters.Add("$session_id", ev["session_id"].Value);
|
||||
parameters.Add("$timestamp", GAState.SessionStart);
|
||||
parameters.Add("$event", jsonDefaults);
|
||||
GAStore.ExecuteQuerySync(sql, parameters);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GALogger.E("addEventToStoreWithEventData: error using json");
|
||||
GALogger.E(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddDimensionsToEvent(JSONObject eventData)
|
||||
{
|
||||
if (eventData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// add to dict (if not nil)
|
||||
if (!string.IsNullOrEmpty(GAState.CurrentCustomDimension01))
|
||||
{
|
||||
eventData["custom_01"] = GAState.CurrentCustomDimension01;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(GAState.CurrentCustomDimension02))
|
||||
{
|
||||
eventData["custom_02"] = GAState.CurrentCustomDimension02;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(GAState.CurrentCustomDimension03))
|
||||
{
|
||||
eventData["custom_03"] = GAState.CurrentCustomDimension03;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddFieldsToEvent(JSONObject eventData, JSONObject fields)
|
||||
{
|
||||
if (eventData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(fields != null && fields.Count > 0)
|
||||
{
|
||||
eventData["custom_fields"] = fields;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResourceFlowTypeToString(EGAResourceFlowType value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case EGAResourceFlowType.Source:
|
||||
{
|
||||
return "Source";
|
||||
}
|
||||
|
||||
case EGAResourceFlowType.Sink:
|
||||
{
|
||||
return "Sink";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ProgressionStatusToString(EGAProgressionStatus value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case EGAProgressionStatus.Start:
|
||||
{
|
||||
return "Start";
|
||||
}
|
||||
|
||||
case EGAProgressionStatus.Complete:
|
||||
{
|
||||
return "Complete";
|
||||
}
|
||||
|
||||
case EGAProgressionStatus.Fail:
|
||||
{
|
||||
return "Fail";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateSessionTime()
|
||||
{
|
||||
if(GAState.SessionIsStarted())
|
||||
{
|
||||
JSONObject ev = GAState.GetEventAnnotations();
|
||||
string jsonDefaults = ev.SaveToBinaryBase64();
|
||||
string sql = "INSERT OR REPLACE INTO ga_session(session_id, timestamp, event) VALUES($session_id, $timestamp, $event);";
|
||||
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
parameters.Add("$session_id", ev["session_id"].Value);
|
||||
parameters.Add("$timestamp", GAState.SessionStart);
|
||||
parameters.Add("$event", jsonDefaults);
|
||||
GAStore.ExecuteQuerySync(sql, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ErrorSeverityToString(EGAErrorSeverity value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case EGAErrorSeverity.Debug:
|
||||
{
|
||||
return "debug";
|
||||
}
|
||||
|
||||
case EGAErrorSeverity.Info:
|
||||
{
|
||||
return "info";
|
||||
}
|
||||
|
||||
case EGAErrorSeverity.Warning:
|
||||
{
|
||||
return "warning";
|
||||
}
|
||||
|
||||
case EGAErrorSeverity.Error:
|
||||
{
|
||||
return "error";
|
||||
}
|
||||
|
||||
case EGAErrorSeverity.Critical:
|
||||
{
|
||||
return "critical";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // Private methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
#if UNITY
|
||||
using UnityEngine;
|
||||
#endif
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
using Windows.System.Threading;
|
||||
#else
|
||||
using System.Threading;
|
||||
#endif
|
||||
|
||||
namespace Foundation.Tasks
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Describes the Tasks State
|
||||
/// </summary>
|
||||
public enum TaskStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Working
|
||||
/// </summary>
|
||||
Pending,
|
||||
/// <summary>
|
||||
/// Exception as thrown or otherwise stopped early
|
||||
/// </summary>
|
||||
Faulted,
|
||||
/// <summary>
|
||||
/// Complete without error
|
||||
/// </summary>
|
||||
Success,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execution strategy for the Task
|
||||
/// </summary>
|
||||
public enum TaskStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// Dispatches the task to a background thread
|
||||
/// </summary>
|
||||
BackgroundThread,
|
||||
/// <summary>
|
||||
/// Dispatches the task to the main thread
|
||||
/// </summary>
|
||||
MainThread,
|
||||
/// <summary>
|
||||
/// Dispatches the task to the current thread
|
||||
/// </summary>
|
||||
CurrentThread,
|
||||
/// <summary>
|
||||
/// Runs the task as a coroutine
|
||||
/// </summary>
|
||||
Coroutine,
|
||||
/// <summary>
|
||||
/// Does nothing. For custom tasks.
|
||||
/// </summary>
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A task encapsulates future work that may be waited on.
|
||||
/// - Support running actions in background threads
|
||||
/// - Supports running coroutines with return results
|
||||
/// - Use the WaitForRoutine method to wait for the task in a coroutine
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var task = Task.Run(() =>
|
||||
/// {
|
||||
/// //Debug.Log does not work in
|
||||
/// Debug.Log("Sleeping...");
|
||||
/// Task.Delay(2000);
|
||||
/// Debug.Log("Slept");
|
||||
/// });
|
||||
/// // wait for it
|
||||
/// yield return task;
|
||||
///
|
||||
/// // check exceptions
|
||||
/// if(task.IsFaulted)
|
||||
/// Debug.LogException(task.Exception)
|
||||
///</code>
|
||||
///</example>
|
||||
public partial class AsyncTask :
|
||||
#if UNITY_5
|
||||
CustomYieldInstruction,
|
||||
#endif
|
||||
IDisposable
|
||||
{
|
||||
#region options
|
||||
/// <summary>
|
||||
/// Forces use of a single thread for debugging
|
||||
/// </summary>
|
||||
public static bool DisableMultiThread = false;
|
||||
|
||||
/// <summary>
|
||||
/// Logs Exceptions
|
||||
/// </summary>
|
||||
public static bool LogErrors = false;
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
/// <summary>
|
||||
/// Run execution path
|
||||
/// </summary>
|
||||
public TaskStrategy Strategy;
|
||||
|
||||
/// <summary>
|
||||
/// Error
|
||||
/// </summary>
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Run State
|
||||
/// </summary>
|
||||
public TaskStatus Status { get; set; }
|
||||
|
||||
#if UNITY_5
|
||||
/// <summary>
|
||||
/// Custom Yield
|
||||
/// </summary>
|
||||
public override bool keepWaiting
|
||||
{
|
||||
get { return !IsCompleted; }
|
||||
}
|
||||
#endif
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get { return Status == TaskStatus.Pending; }
|
||||
}
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get { return (Status == TaskStatus.Success || Status == TaskStatus.Faulted) && !HasContinuations; }
|
||||
}
|
||||
|
||||
public bool IsFaulted
|
||||
{
|
||||
get { return Status == TaskStatus.Faulted; }
|
||||
}
|
||||
|
||||
public bool IsSuccess
|
||||
{
|
||||
get { return Status == TaskStatus.Success; }
|
||||
}
|
||||
|
||||
public bool HasContinuations { get; protected set; }
|
||||
#endregion
|
||||
|
||||
#region private
|
||||
|
||||
protected TaskStatus _status;
|
||||
protected Action _action;
|
||||
protected IEnumerator _routine;
|
||||
List<Delegate> _completeList;
|
||||
|
||||
#endregion
|
||||
|
||||
#region constructor
|
||||
|
||||
static AsyncTask()
|
||||
{
|
||||
#if UNITY
|
||||
TaskManager.ConfirmInit();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new task
|
||||
/// </summary>
|
||||
public AsyncTask()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new task
|
||||
/// </summary>
|
||||
public AsyncTask(TaskStrategy mode)
|
||||
{
|
||||
Strategy = mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Task in a Faulted state
|
||||
/// </summary>
|
||||
/// <param name="ex"></param>
|
||||
public AsyncTask(Exception ex)
|
||||
{
|
||||
Exception = ex;
|
||||
Strategy = TaskStrategy.Custom;
|
||||
Status = TaskStatus.Faulted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new background task
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public AsyncTask(Action action)
|
||||
{
|
||||
_action = action;
|
||||
Strategy = TaskStrategy.BackgroundThread;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Task
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="mode"></param>
|
||||
public AsyncTask(Action action, TaskStrategy mode)
|
||||
: this()
|
||||
{
|
||||
if (mode == TaskStrategy.Coroutine)
|
||||
throw new ArgumentException("Action tasks may not be coroutines");
|
||||
|
||||
_action = action;
|
||||
Strategy = mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Coroutine Task
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public AsyncTask(IEnumerator action)
|
||||
: this()
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException("action");
|
||||
|
||||
_routine = action;
|
||||
Strategy = TaskStrategy.Coroutine;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private
|
||||
|
||||
protected virtual void Execute()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_action != null)
|
||||
{
|
||||
_action();
|
||||
}
|
||||
Status = TaskStatus.Success;
|
||||
OnTaskComplete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception = ex;
|
||||
Status = TaskStatus.Faulted;
|
||||
|
||||
#if UNITY
|
||||
if (LogErrors)
|
||||
Debug.LogException(ex);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
protected async void RunOnBackgroundThread()
|
||||
{
|
||||
Status = TaskStatus.Pending;
|
||||
await ThreadPool.RunAsync(o => Execute());
|
||||
#else
|
||||
protected void RunOnBackgroundThread()
|
||||
{
|
||||
Status = TaskStatus.Pending;
|
||||
ThreadPool.QueueUserWorkItem(state => Execute());
|
||||
#endif
|
||||
}
|
||||
|
||||
protected void RunOnCurrentThread()
|
||||
{
|
||||
Status = TaskStatus.Pending;
|
||||
Execute();
|
||||
}
|
||||
|
||||
#if UNITY
|
||||
protected void RunOnMainThread()
|
||||
{
|
||||
Status = TaskStatus.Pending;
|
||||
TaskManager.RunOnMainThread(Execute);
|
||||
}
|
||||
|
||||
protected void RunAsCoroutine()
|
||||
{
|
||||
Status = TaskStatus.Pending;
|
||||
|
||||
TaskManager.StartRoutine(new TaskManager.CoroutineCommand
|
||||
{
|
||||
Coroutine = _routine,
|
||||
OnComplete = OnRoutineComplete
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
protected virtual void OnTaskComplete()
|
||||
{
|
||||
if (_completeList != null)
|
||||
{
|
||||
foreach (var d in _completeList)
|
||||
{
|
||||
if (d != null)
|
||||
d.DynamicInvoke(this);
|
||||
}
|
||||
_completeList = null;
|
||||
}
|
||||
HasContinuations = false;
|
||||
}
|
||||
|
||||
protected void OnRoutineComplete()
|
||||
{
|
||||
if (Status == TaskStatus.Pending)
|
||||
{
|
||||
Status = TaskStatus.Success;
|
||||
OnTaskComplete();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public methods
|
||||
|
||||
/// <summary>
|
||||
/// Runs complete logic, for custom tasks
|
||||
/// </summary>
|
||||
public virtual void Complete(Exception ex = null)
|
||||
{
|
||||
if (ex == null)
|
||||
{
|
||||
Exception = null;
|
||||
Status = TaskStatus.Success;
|
||||
OnTaskComplete();
|
||||
}
|
||||
else
|
||||
{
|
||||
Exception = ex;
|
||||
Status = TaskStatus.Faulted;
|
||||
OnTaskComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the task
|
||||
/// </summary>
|
||||
public virtual void Start()
|
||||
{
|
||||
Status = TaskStatus.Pending;
|
||||
|
||||
switch (Strategy)
|
||||
{
|
||||
|
||||
case TaskStrategy.Custom:
|
||||
break;
|
||||
#if UNITY
|
||||
case TaskStrategy.Coroutine:
|
||||
RunAsCoroutine();
|
||||
break;
|
||||
#endif
|
||||
case TaskStrategy.BackgroundThread:
|
||||
if (DisableMultiThread)
|
||||
RunOnCurrentThread();
|
||||
else
|
||||
RunOnBackgroundThread();
|
||||
break;
|
||||
case TaskStrategy.CurrentThread:
|
||||
RunOnCurrentThread();
|
||||
break;
|
||||
#if UNITY
|
||||
case TaskStrategy.MainThread:
|
||||
RunOnMainThread();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Status = TaskStatus.Pending;
|
||||
Exception = null;
|
||||
_action = null;
|
||||
_routine = null;
|
||||
_completeList = null;
|
||||
HasContinuations = false;
|
||||
}
|
||||
|
||||
public void AddContinue(Delegate action)
|
||||
{
|
||||
HasContinuations = true;
|
||||
if (_completeList == null)
|
||||
{
|
||||
_completeList = new List<Delegate>();
|
||||
}
|
||||
|
||||
_completeList.Add(action);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#if UNITY
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Foundation.Tasks
|
||||
{
|
||||
public static class TaskExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// will throw if faulted
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static T ThrowIfFaulted<T>(this T self) where T : AsyncTask
|
||||
{
|
||||
if (self.IsFaulted)
|
||||
throw self.Exception;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the task to complete
|
||||
/// </summary>
|
||||
public static T ContinueWith<T>(this T self, Action<T> continuation) where T : AsyncTask
|
||||
{
|
||||
if (self.IsCompleted)
|
||||
{
|
||||
continuation(self);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.AddContinue(continuation);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a timeout to the task. Will raise an exception if still running
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="self"></param>
|
||||
/// <param name="seconds"></param>
|
||||
/// <param name="onTimeout"></param>
|
||||
/// <returns></returns>
|
||||
public static T AddTimeout<T>(this T self, int seconds, Action<AsyncTask> onTimeout = null) where T : AsyncTask
|
||||
{
|
||||
TaskManager.StartRoutine(TimeOutAsync(self, seconds, onTimeout));
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static IEnumerator TimeOutAsync(AsyncTask task, int seconds, Action<AsyncTask> onTimeout = null)
|
||||
{
|
||||
yield return new WaitForSeconds(seconds);
|
||||
|
||||
if (task.IsRunning)
|
||||
{
|
||||
if (onTimeout != null)
|
||||
{
|
||||
onTimeout(task);
|
||||
}
|
||||
|
||||
task.Complete(new Exception("Timeout"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Foundation.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// A task encapsulates future work that may be waited on.
|
||||
/// - Support running actions in background threads
|
||||
/// - Supports running coroutines with return results
|
||||
/// - Use the WaitForRoutine method to wait for the task in a coroutine
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var task = Task.Run(() =>
|
||||
/// {
|
||||
/// //Debug.Log does not work in
|
||||
/// Debug.Log("Sleeping...");
|
||||
/// Task.Delay(2000);
|
||||
/// Debug.Log("Slept");
|
||||
/// });
|
||||
/// // wait for it
|
||||
/// yield return task;
|
||||
///
|
||||
/// // check exceptions
|
||||
/// if(task.IsFaulted)
|
||||
/// Debug.LogException(task.Exception)
|
||||
///</code>
|
||||
///</example>
|
||||
public partial class AsyncTask
|
||||
{
|
||||
#region Task
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask Run(Action action)
|
||||
{
|
||||
var task = new AsyncTask(action);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask RunOnMain(Action action)
|
||||
{
|
||||
var task = new AsyncTask(action, TaskStrategy.MainThread);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask RunOnCurrent(Action action)
|
||||
{
|
||||
var task = new AsyncTask(action, TaskStrategy.CurrentThread);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Coroutine
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask RunCoroutine(IEnumerator function)
|
||||
{
|
||||
var task = new AsyncTask(function);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask RunCoroutine(Func<IEnumerator> function)
|
||||
{
|
||||
var task = new AsyncTask(function());
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask RunCoroutine(Func<AsyncTask, IEnumerator> function)
|
||||
{
|
||||
var task = new AsyncTask();
|
||||
task.Strategy = TaskStrategy.Coroutine;
|
||||
task._routine = function(task);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#if UNITY
|
||||
#region Task With Result
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask<TResult> Run<TResult>(Func<TResult> function)
|
||||
{
|
||||
var task = new AsyncTask<TResult>(function);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask<TResult> RunOnMain<TResult>(Func<TResult> function)
|
||||
{
|
||||
var task = new AsyncTask<TResult>(function, TaskStrategy.MainThread);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask<TResult> RunOnCurrent<TResult>(Func<TResult> function)
|
||||
{
|
||||
var task = new AsyncTask<TResult>(function, TaskStrategy.CurrentThread);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new running task
|
||||
/// </summary>
|
||||
public static AsyncTask<TResult> RunCoroutine<TResult>(IEnumerator function)
|
||||
{
|
||||
var task = new AsyncTask<TResult>(function);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a task which passes the task as a parameter
|
||||
/// </summary>
|
||||
public static AsyncTask<TResult> RunCoroutine<TResult>(Func<AsyncTask<TResult>, IEnumerator> function)
|
||||
{
|
||||
var task = new AsyncTask<TResult>();
|
||||
task.Strategy = TaskStrategy.Coroutine;
|
||||
task._routine = function(task);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region success / fails
|
||||
|
||||
/// <summary>
|
||||
/// A default task in the success state
|
||||
/// </summary>
|
||||
static AsyncTask _successTask = new AsyncTask(TaskStrategy.Custom) { Status = TaskStatus.Success };
|
||||
|
||||
/// <summary>
|
||||
/// A default task in the success state
|
||||
/// </summary>
|
||||
public static AsyncTask<T> SuccessTask<T>(T result)
|
||||
{
|
||||
return new AsyncTask<T>(TaskStrategy.Custom) { Status = TaskStatus.Success, Result = result };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A default task in the faulted state
|
||||
/// </summary>
|
||||
public static AsyncTask SuccessTask()
|
||||
{
|
||||
return _successTask;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A default task in the faulted state
|
||||
/// </summary>
|
||||
public static AsyncTask FailedTask(string exception)
|
||||
{
|
||||
return FailedTask(new Exception(exception));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A default task in the faulted state
|
||||
/// </summary>
|
||||
public static AsyncTask FailedTask(Exception ex)
|
||||
{
|
||||
return new AsyncTask(TaskStrategy.Custom) { Status = TaskStatus.Faulted, Exception = ex };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A default task in the faulted state
|
||||
/// </summary>
|
||||
public static AsyncTask<T> FailedTask<T>(string exception)
|
||||
{
|
||||
return FailedTask<T>(new Exception(exception));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A default task in the faulted state
|
||||
/// </summary>
|
||||
public static AsyncTask<T> FailedTask<T>(Exception ex)
|
||||
{
|
||||
return new AsyncTask<T>(TaskStrategy.Custom) { Status = TaskStatus.Faulted, Exception = ex };
|
||||
}
|
||||
#endregion
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# if !WINDOWS_WSA && !WINDOWS_UWP
|
||||
using System.Threading;
|
||||
|
||||
namespace Foundation.Tasks
|
||||
{
|
||||
public partial class TaskManager
|
||||
{ /// <summary>
|
||||
/// Checks if this is the main thread
|
||||
/// </summary>
|
||||
public static bool IsMainThread
|
||||
{
|
||||
get { return Thread.CurrentThread == MainThread; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Main Thread
|
||||
/// </summary>
|
||||
public static Thread MainThread { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Current Thread
|
||||
/// </summary>
|
||||
public static Thread CurrentThread
|
||||
{
|
||||
get
|
||||
{
|
||||
return Thread.CurrentThread;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
using System;
|
||||
|
||||
namespace Foundation.Tasks
|
||||
{
|
||||
public partial class TaskManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if this is the main thread
|
||||
/// </summary>
|
||||
public static bool IsMainThread
|
||||
{
|
||||
get { return Environment.CurrentManagedThreadId == MainThread; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Main Thread
|
||||
/// </summary>
|
||||
public static int MainThread { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Current Thread
|
||||
/// </summary>
|
||||
public static int CurrentThread
|
||||
{
|
||||
get { return Environment.CurrentManagedThreadId; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,308 @@
|
||||
#if UNITY
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Foundation.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Manager for running coroutines and scheduling actions to runs in the main thread.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Self instantiating. No need to add to scene.
|
||||
/// </remarks>
|
||||
[AddComponentMenu("Foundation/TaskManager")]
|
||||
[ExecuteInEditMode]
|
||||
public partial class TaskManager : MonoBehaviour
|
||||
{
|
||||
|
||||
#region sub
|
||||
/// <summary>
|
||||
/// Thread Safe logger command
|
||||
/// </summary>
|
||||
public struct LogCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Color Code
|
||||
/// </summary>
|
||||
public LogType Type;
|
||||
/// <summary>
|
||||
/// Text
|
||||
/// </summary>
|
||||
public object Message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thread safe coroutine command
|
||||
/// </summary>
|
||||
public struct CoroutineCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// The IEnumerator Coroutine
|
||||
/// </summary>
|
||||
public IEnumerator Coroutine;
|
||||
/// <summary>
|
||||
/// Called on complete
|
||||
/// </summary>
|
||||
public Action OnComplete;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Static Accessor
|
||||
/// </summary>
|
||||
public static TaskManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
ConfirmInit();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Confirms the instance is ready for use
|
||||
/// </summary>
|
||||
public static void ConfirmInit()
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
var old = FindObjectsOfType<TaskManager>();
|
||||
foreach (var manager in old)
|
||||
{
|
||||
if (Application.isEditor)
|
||||
DestroyImmediate(manager.gameObject);
|
||||
else
|
||||
Destroy(manager.gameObject);
|
||||
}
|
||||
|
||||
|
||||
var go = new GameObject("_TaskManager");
|
||||
DontDestroyOnLoad(go);
|
||||
_instance = go.AddComponent<TaskManager>();
|
||||
|
||||
MainThread = CurrentThread;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scheduled the routine to run (on the main thread)
|
||||
/// </summary>
|
||||
public static Coroutine WaitForSeconds(int seconds)
|
||||
{
|
||||
return Instance.StartCoroutine(Instance.WaitForSecondsInternal(seconds));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scheduled the routine to run (on the main thread)
|
||||
/// </summary>
|
||||
public static Coroutine StartRoutine(IEnumerator coroutine)
|
||||
{
|
||||
if (IsApplicationQuit)
|
||||
return null;
|
||||
|
||||
//Make sure we are in the main thread
|
||||
if (!IsMainThread)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
PendingAdd.Add(coroutine);
|
||||
|
||||
//Debug.LogWarning("Running coroutines from background thread are not awaitable. Use CoroutineInfo");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return Instance.StartCoroutine(coroutine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scheduled the routine to run (on the main thread)
|
||||
/// </summary>
|
||||
public static void StartRoutine(CoroutineCommand info)
|
||||
{
|
||||
if (IsApplicationQuit)
|
||||
return;
|
||||
|
||||
//Make sure we are in the main thread
|
||||
if (!IsMainThread)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
PendingCoroutineInfo.Add(info);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance.StartCoroutine(Instance.RunCoroutineInfo(info));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scheduled the routine to run (on the main thread)
|
||||
/// </summary>
|
||||
public static void StopRoutine(IEnumerator coroutine)
|
||||
{
|
||||
if (IsApplicationQuit)
|
||||
return;
|
||||
|
||||
//Make sure we are in the main thread
|
||||
if (!IsMainThread)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
PendingRemove.Add(coroutine);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance.StopCoroutine(coroutine);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the action to run on the main thread
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public static void RunOnMainThread(Action action)
|
||||
{
|
||||
if (IsApplicationQuit)
|
||||
return;
|
||||
|
||||
//Make sure we are in the main thread
|
||||
if (!IsMainThread)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
PendingActions.Add(action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A thread safe logger
|
||||
/// </summary>
|
||||
/// <param name="m"></param>
|
||||
public static void Log(LogCommand m)
|
||||
{
|
||||
if (!IsMainThread)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
PendingLogs.Add(m);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(m);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void Write(LogCommand m)
|
||||
{
|
||||
switch (m.Type)
|
||||
{
|
||||
case LogType.Warning:
|
||||
Debug.LogWarning(m.Message);
|
||||
break;
|
||||
case LogType.Error:
|
||||
case LogType.Exception:
|
||||
Debug.LogError(m.Message);
|
||||
break;
|
||||
case LogType.Log:
|
||||
case LogType.Assert:
|
||||
Debug.Log(m.Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static TaskManager _instance;
|
||||
private static object syncRoot = new object();
|
||||
protected static readonly List<CoroutineCommand> PendingCoroutineInfo = new List<CoroutineCommand>();
|
||||
protected static readonly List<IEnumerator> PendingAdd = new List<IEnumerator>();
|
||||
protected static readonly List<IEnumerator> PendingRemove = new List<IEnumerator>();
|
||||
protected static readonly List<Action> PendingActions = new List<Action>();
|
||||
protected static readonly List<LogCommand> PendingLogs = new List<LogCommand>();
|
||||
protected static bool IsApplicationQuit;
|
||||
|
||||
protected void Awake()
|
||||
{
|
||||
if (_instance == null)
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
protected void Update()
|
||||
{
|
||||
if (IsApplicationQuit)
|
||||
return;
|
||||
|
||||
if (PendingAdd.Count == 0 && PendingRemove.Count == 0 && PendingActions.Count == 0 && PendingLogs.Count == 0 && PendingCoroutineInfo.Count == 0)
|
||||
return;
|
||||
|
||||
lock (syncRoot)
|
||||
{
|
||||
for (int i = 0;i < PendingLogs.Count;i++)
|
||||
{
|
||||
Write(PendingLogs[i]);
|
||||
}
|
||||
for (int i = 0;i < PendingAdd.Count;i++)
|
||||
{
|
||||
StartCoroutine(PendingAdd[i]);
|
||||
}
|
||||
for (int i = 0;i < PendingRemove.Count;i++)
|
||||
{
|
||||
StopCoroutine(PendingRemove[i]);
|
||||
}
|
||||
for (int i = 0;i < PendingCoroutineInfo.Count;i++)
|
||||
{
|
||||
StartCoroutine(RunCoroutineInfo(PendingCoroutineInfo[i]));
|
||||
}
|
||||
for (int i = 0;i < PendingActions.Count;i++)
|
||||
{
|
||||
PendingActions[i]();
|
||||
}
|
||||
PendingAdd.Clear();
|
||||
PendingRemove.Clear();
|
||||
PendingActions.Clear();
|
||||
PendingLogs.Clear();
|
||||
PendingCoroutineInfo.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator RunCoroutineInfo(CoroutineCommand info)
|
||||
{
|
||||
yield return StartCoroutine(info.Coroutine);
|
||||
|
||||
if (info.OnComplete != null)
|
||||
info.OnComplete();
|
||||
}
|
||||
|
||||
protected void OnApplicationQuit()
|
||||
{
|
||||
IsApplicationQuit = true;
|
||||
}
|
||||
|
||||
IEnumerator WaitForSecondsInternal(int seconds)
|
||||
{
|
||||
if(seconds <= 0)
|
||||
yield break;
|
||||
|
||||
var delta = 0f;
|
||||
|
||||
while (delta < seconds)
|
||||
{
|
||||
delta += Time.unscaledDeltaTime;
|
||||
yield return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,173 @@
|
||||
#if UNITY
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Foundation.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// A task encapsulates future work that may be waited on.
|
||||
/// - Support running actions in background threads
|
||||
/// - Supports running coroutines with return results
|
||||
/// - Use the WaitForRoutine method to wait for the task in a coroutine
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var task = Task.Run(() =>
|
||||
/// {
|
||||
/// //Debug.Log does not work in
|
||||
/// Debug.Log("Sleeping...");
|
||||
/// Task.Delay(2000);
|
||||
/// Debug.Log("Slept");
|
||||
/// });
|
||||
/// // wait for it
|
||||
/// yield return task;
|
||||
///
|
||||
/// // check exceptions
|
||||
/// if(task.IsFaulted)
|
||||
/// Debug.LogException(task.Exception)
|
||||
///</code>
|
||||
///</example>
|
||||
public class AsyncTask<TResult> : AsyncTask
|
||||
{
|
||||
#region public fields
|
||||
|
||||
/// <summary>
|
||||
/// get the result of the task. Blocking. It is recommended you yield on the wait before accessing this value
|
||||
/// </summary>
|
||||
public TResult Result;
|
||||
#endregion
|
||||
|
||||
#region ctor
|
||||
|
||||
Func<TResult> _function;
|
||||
|
||||
public AsyncTask()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the task in the Success state.
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
public AsyncTask(TResult result)
|
||||
: this()
|
||||
{
|
||||
Status = TaskStatus.Success;
|
||||
Strategy = TaskStrategy.Custom;
|
||||
Result = result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new background Task strategy
|
||||
/// </summary>
|
||||
/// <param name="function"></param>
|
||||
public AsyncTask(Func<TResult> function)
|
||||
: this()
|
||||
{
|
||||
if (function == null)
|
||||
throw new ArgumentNullException("function");
|
||||
|
||||
_function = function;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new task with a specific strategy
|
||||
/// </summary>
|
||||
public AsyncTask(Func<TResult> function, TaskStrategy mode)
|
||||
: this()
|
||||
{
|
||||
if (function == null)
|
||||
throw new ArgumentNullException("function");
|
||||
|
||||
if (mode == TaskStrategy.Coroutine)
|
||||
throw new ArgumentException("Mode can not be coroutine");
|
||||
|
||||
_function = function;
|
||||
Strategy = mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Coroutine task
|
||||
/// </summary>
|
||||
public AsyncTask(IEnumerator routine)
|
||||
{
|
||||
if (routine == null)
|
||||
throw new ArgumentNullException("routine");
|
||||
|
||||
|
||||
_routine = routine;
|
||||
Strategy = TaskStrategy.Coroutine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Task in a Faulted state
|
||||
/// </summary>
|
||||
/// <param name="ex"></param>
|
||||
public AsyncTask(Exception ex)
|
||||
{
|
||||
Exception = ex;
|
||||
Strategy = TaskStrategy.Custom;
|
||||
Status = TaskStatus.Faulted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new task
|
||||
/// </summary>
|
||||
public AsyncTask(TaskStrategy mode)
|
||||
: this()
|
||||
{
|
||||
Strategy = mode;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region protected methods
|
||||
|
||||
/// <summary>
|
||||
/// Runs complete logic, for custom tasks
|
||||
/// </summary>
|
||||
public override void Complete(Exception ex = null)
|
||||
{
|
||||
Result = default(TResult);
|
||||
base.Complete(ex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs complete logic, for custom tasks
|
||||
/// </summary>
|
||||
public void Complete(TResult result)
|
||||
{
|
||||
Result = result;
|
||||
base.Complete();
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
Result = default(TResult);
|
||||
base.Start();
|
||||
}
|
||||
|
||||
protected override void Execute()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_function != null)
|
||||
{
|
||||
Result = _function();
|
||||
}
|
||||
Status = TaskStatus.Success;
|
||||
OnTaskComplete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception = ex;
|
||||
Status = TaskStatus.Faulted;
|
||||
if (LogErrors)
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>1577d2e0-959b-4106-a7d8-d77b41c09f0c</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>GA_SDK_MONO_SHARED</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Device\GADevice.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EGAErrorSeverity.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EGAGender.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EGALoggerMessageType.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EGAProgressionStatus.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EGAResourceFlowType.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Events\GAEvents.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Foundation\Tasks\Task.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Foundation\Tasks\TaskExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Foundation\Tasks\TaskFactory.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Foundation\Tasks\TaskManager.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Foundation\Tasks\TaskManager.Net.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Foundation\Tasks\TaskManager.WSA.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Foundation\Tasks\TaskTResult.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)GameAnalytics.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Http\EGAHTTPApiResponse.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Http\EGASdkErrorType.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Http\GAHTTPApi.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ICommandCenterListener.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Logging\GALogger.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)State\GAState.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Store\GAStore.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Tasks\SdkErrorTask.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Threading\GAThreading.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Threading\PriorityQueue.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Threading\TimedBlock.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Utilities\GAUtilities.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Utilities\SimpleJSON.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Utilities\SimpleJSONBinary.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Validators\GAValidator.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>1577d2e0-959b-4106-a7d8-d77b41c09f0c</ProjectGuid>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<PropertyGroup />
|
||||
<Import Project="GA-SDK-MONO-SHARED.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
||||
839
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/GameAnalytics.cs
Normal file
839
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/GameAnalytics.cs
Normal file
@@ -0,0 +1,839 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GameAnalyticsSDK.Net.Threading;
|
||||
using GameAnalyticsSDK.Net.Logging;
|
||||
using GameAnalyticsSDK.Net.State;
|
||||
using GameAnalyticsSDK.Net.Validators;
|
||||
using GameAnalyticsSDK.Net.Device;
|
||||
using GameAnalyticsSDK.Net.Events;
|
||||
using GameAnalyticsSDK.Net.Store;
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using System.Threading.Tasks;
|
||||
#else
|
||||
using System.Threading;
|
||||
#endif
|
||||
|
||||
namespace GameAnalyticsSDK.Net
|
||||
{
|
||||
public static class GameAnalytics
|
||||
{
|
||||
private static bool _endThread;
|
||||
|
||||
static GameAnalytics()
|
||||
{
|
||||
_endThread = false;
|
||||
GADevice.Touch();
|
||||
}
|
||||
|
||||
#if !UNITY && !MONO
|
||||
public static event Action<string, EGALoggerMessageType> OnMessageLogged;
|
||||
|
||||
internal static void MessageLogged(string message, EGALoggerMessageType type)
|
||||
{
|
||||
OnMessageLogged?.Invoke(message, type);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#region CONFIGURE
|
||||
|
||||
public static void ConfigureAvailableCustomDimensions01(params string[] customDimensions)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("configureAvailableCustomDimensions01", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
GALogger.W("Available custom dimensions must be set before SDK is initialized");
|
||||
return;
|
||||
}
|
||||
GAState.AvailableCustomDimensions01 = customDimensions;
|
||||
});
|
||||
}
|
||||
|
||||
public static void ConfigureAvailableCustomDimensions02(params string[] customDimensions)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("configureAvailableCustomDimensions02", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
GALogger.W("Available custom dimensions must be set before SDK is initialized");
|
||||
return;
|
||||
}
|
||||
GAState.AvailableCustomDimensions02 = customDimensions;
|
||||
});
|
||||
}
|
||||
|
||||
public static void ConfigureAvailableCustomDimensions03(params string[] customDimensions)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("configureAvailableCustomDimensions03", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
GALogger.W("Available custom dimensions must be set before SDK is initialized");
|
||||
return;
|
||||
}
|
||||
GAState.AvailableCustomDimensions03 = customDimensions;
|
||||
});
|
||||
}
|
||||
|
||||
public static void ConfigureAvailableResourceCurrencies(params string[] resourceCurrencies)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("configureAvailableResourceCurrencies", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
GALogger.W("Available resource currencies must be set before SDK is initialized");
|
||||
return;
|
||||
}
|
||||
GAState.AvailableResourceCurrencies = resourceCurrencies;
|
||||
});
|
||||
}
|
||||
|
||||
public static void ConfigureAvailableResourceItemTypes(params string[] resourceItemTypes)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("configureAvailableResourceItemTypes", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
GALogger.W("Available resource item types must be set before SDK is initialized");
|
||||
return;
|
||||
}
|
||||
GAState.AvailableResourceItemTypes = resourceItemTypes;
|
||||
});
|
||||
}
|
||||
|
||||
public static void ConfigureBuild(string build)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("configureBuild", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
GALogger.W("Build version must be set before SDK is initialized.");
|
||||
return;
|
||||
}
|
||||
if (!GAValidator.ValidateBuild(build))
|
||||
{
|
||||
GALogger.I("Validation fail - configure build: Cannot be null, empty or above 32 length. String: " + build);
|
||||
return;
|
||||
}
|
||||
GAState.Build = build;
|
||||
});
|
||||
}
|
||||
|
||||
public static void ConfigureSdkGameEngineVersion(string sdkGameEngineVersion)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("configureSdkGameEngineVersion", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!GAValidator.ValidateSdkWrapperVersion(sdkGameEngineVersion))
|
||||
{
|
||||
GALogger.I("Validation fail - configure sdk version: Sdk version not supported. String: " + sdkGameEngineVersion);
|
||||
return;
|
||||
}
|
||||
GADevice.SdkGameEngineVersion = sdkGameEngineVersion;
|
||||
});
|
||||
}
|
||||
|
||||
public static void ConfigureGameEngineVersion(string gameEngineVersion)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("configureGameEngineVersion", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!GAValidator.ValidateEngineVersion(gameEngineVersion))
|
||||
{
|
||||
GALogger.I("Validation fail - configure sdk version: Sdk version not supported. String: " + gameEngineVersion);
|
||||
return;
|
||||
}
|
||||
GADevice.GameEngineVersion = gameEngineVersion;
|
||||
});
|
||||
}
|
||||
|
||||
public static void ConfigureUserId(string uId)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("configureUserId", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
GALogger.W("A custom user id must be set before SDK is initialized.");
|
||||
return;
|
||||
}
|
||||
if (!GAValidator.ValidateUserId(uId))
|
||||
{
|
||||
GALogger.I("Validation fail - configure user_id: Cannot be null, empty or above 64 length. Will use default user_id method. Used string: " + uId);
|
||||
return;
|
||||
}
|
||||
|
||||
GAState.UserId = uId;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion // CONFIGURE
|
||||
|
||||
#region INITIALIZE
|
||||
|
||||
public static void Initialize(string gameKey, string gameSecret)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
CoreApplication.Suspending += OnSuspending;
|
||||
CoreApplication.Resuming += OnResuming;
|
||||
#endif
|
||||
GADevice.UpdateConnectionType();
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("initialize", () =>
|
||||
{
|
||||
if (IsSdkReady(true, false))
|
||||
{
|
||||
GALogger.W("SDK already initialized. Can only be called once.");
|
||||
return;
|
||||
}
|
||||
if (!GAValidator.ValidateKeys(gameKey, gameSecret))
|
||||
{
|
||||
GALogger.W("SDK failed initialize. Game key or secret key is invalid. Can only contain characters A-z 0-9, gameKey is 32 length, gameSecret is 40 length. Failed keys - gameKey: " + gameKey + ", secretKey: " + gameSecret);
|
||||
return;
|
||||
}
|
||||
|
||||
GAState.SetKeys(gameKey, gameSecret);
|
||||
|
||||
if (!GAStore.EnsureDatabase(false, gameKey))
|
||||
{
|
||||
GALogger.W("Could not ensure/validate local event database: " + GADevice.WritablePath);
|
||||
}
|
||||
|
||||
GAState.InternalInitialize();
|
||||
});
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
private static async void OnSuspending(object sender, SuspendingEventArgs e)
|
||||
{
|
||||
var deferral = e.SuspendingOperation.GetDeferral();
|
||||
await WaitOnSuspend();
|
||||
deferral.Complete();
|
||||
}
|
||||
|
||||
private static async Task WaitOnSuspend()
|
||||
{
|
||||
if (!GAState.UseManualSessionHandling)
|
||||
{
|
||||
OnSuspend();
|
||||
|
||||
while (!GAThreading.IsThreadFinished())
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GALogger.I("OnSuspending: Not calling GameAnalytics.OnStop() as using manual session handling");
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnResuming(object sender, object e)
|
||||
{
|
||||
GAThreading.PerformTaskOnGAThread("onResuming", () =>
|
||||
{
|
||||
if(!GAState.UseManualSessionHandling)
|
||||
{
|
||||
OnResume();
|
||||
}
|
||||
else
|
||||
{
|
||||
GALogger.I("OnResuming: Not calling GameAnalytics.OnResume() as using manual session handling");
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion // INITIALIZE
|
||||
|
||||
#region ADD EVENTS
|
||||
|
||||
public static void AddBusinessEvent(string currency, int amount, string itemType, string itemId, string cartType/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GADevice.UpdateConnectionType();
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("addBusinessEvent", () =>
|
||||
{
|
||||
if (!IsSdkReady(true, true, "Could not add business event"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Send to events
|
||||
GAEvents.AddBusinessEvent(currency, amount, itemType, itemId, cartType, null);
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddResourceEvent(EGAResourceFlowType flowType, string currency, float amount, string itemType, string itemId/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GADevice.UpdateConnectionType();
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("addResourceEvent", () =>
|
||||
{
|
||||
if (!IsSdkReady(true, true, "Could not add resource event"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAEvents.AddResourceEvent(flowType, currency, amount, itemType, itemId, null);
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
AddProgressionEvent(progressionStatus, progression01, "", ""/*, fields*/);
|
||||
}
|
||||
|
||||
public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01, double score/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
AddProgressionEvent(progressionStatus, progression01, "", "", score/*, fields*/);
|
||||
}
|
||||
|
||||
public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01, string progression02/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
AddProgressionEvent(progressionStatus, progression01, progression02, ""/*, fields*/);
|
||||
}
|
||||
|
||||
public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01, string progression02, double score/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
AddProgressionEvent(progressionStatus, progression01, progression02, "", score/*, fields*/);
|
||||
}
|
||||
|
||||
public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01, string progression02, string progression03/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GADevice.UpdateConnectionType();
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("addProgressionEvent", () =>
|
||||
{
|
||||
if(!IsSdkReady(true, true, "Could not add progression event"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Send to events
|
||||
// TODO(nikolaj): check if this cast from int to double is OK
|
||||
GAEvents.AddProgressionEvent(progressionStatus, progression01, progression02, progression03, 0, false, null);
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01, string progression02, string progression03, double score/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GADevice.UpdateConnectionType();
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("addProgressionEvent", () =>
|
||||
{
|
||||
if (!IsSdkReady(true, true, "Could not add progression event"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Send to events
|
||||
// TODO(nikolaj): check if this cast from int to double is OK
|
||||
GAEvents.AddProgressionEvent(progressionStatus, progression01, progression02, progression03, score, true, null);
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddDesignEvent(string eventId, IDictionary<string, object> fields = null)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GADevice.UpdateConnectionType();
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("addDesignEvent", () =>
|
||||
{
|
||||
if(!IsSdkReady(true, true, "Could not add design event"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
GAEvents.AddDesignEvent(eventId, 0, false, fields);
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddDesignEvent(string eventId, double value/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GADevice.UpdateConnectionType();
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("addDesignEvent", () =>
|
||||
{
|
||||
if (!IsSdkReady(true, true, "Could not add design event"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
GAEvents.AddDesignEvent(eventId, value, true, null);
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddErrorEvent(EGAErrorSeverity severity, string message/*, IDictionary<string, object> fields = null*/)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GADevice.UpdateConnectionType();
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("addErrorEvent", () =>
|
||||
{
|
||||
if (!IsSdkReady(true, true, "Could not add error event"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
GAEvents.AddErrorEvent(severity, message, null);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion // ADD EVENTS
|
||||
|
||||
#region SET STATE CHANGES WHILE RUNNING
|
||||
|
||||
public static void SetEnabledInfoLog(bool flag)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setEnabledInfoLog", () =>
|
||||
{
|
||||
if (flag)
|
||||
{
|
||||
GALogger.InfoLog = flag;
|
||||
GALogger.I("Info logging enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
GALogger.I("Info logging disabled");
|
||||
GALogger.InfoLog = flag;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetEnabledVerboseLog(bool flag)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setEnabledVerboseLog", () =>
|
||||
{
|
||||
if (flag)
|
||||
{
|
||||
GALogger.VerboseLog = flag;
|
||||
GALogger.I("Verbose logging enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
GALogger.I("Verbose logging disabled");
|
||||
GALogger.VerboseLog = flag;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetEnabledManualSessionHandling(bool flag)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setEnabledManualSessionHandling", () =>
|
||||
{
|
||||
GAState.SetManualSessionHandling(flag);
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetEnabledEventSubmission(bool flag)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setEnabledEventSubmission", () =>
|
||||
{
|
||||
if (flag)
|
||||
{
|
||||
GAState.SetEnabledEventSubmission(flag);
|
||||
GALogger.I("Event submission enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
GALogger.I("Event submission disabled");
|
||||
GAState.SetEnabledEventSubmission(flag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetCustomDimension01(string dimension)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setCustomDimension01", () =>
|
||||
{
|
||||
if (!GAValidator.ValidateDimension01(dimension))
|
||||
{
|
||||
GALogger.W("Could not set custom01 dimension value to '" + dimension + "'. Value not found in available custom01 dimension values");
|
||||
return;
|
||||
}
|
||||
GAState.SetCustomDimension01(dimension);
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetCustomDimension02(string dimension)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setCustomDimension02", () =>
|
||||
{
|
||||
if (!GAValidator.ValidateDimension02(dimension))
|
||||
{
|
||||
GALogger.W("Could not set custom02 dimension value to '" + dimension + "'. Value not found in available custom02 dimension values");
|
||||
return;
|
||||
}
|
||||
GAState.SetCustomDimension02(dimension);
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetCustomDimension03(string dimension)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setCustomDimension03", () =>
|
||||
{
|
||||
if (!GAValidator.ValidateDimension03(dimension))
|
||||
{
|
||||
GALogger.W("Could not set custom03 dimension value to '" + dimension + "'. Value not found in available custom03 dimension values");
|
||||
return;
|
||||
}
|
||||
GAState.SetCustomDimension03(dimension);
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetFacebookId(string facebookId)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setFacebookId", () =>
|
||||
{
|
||||
if (GAValidator.ValidateFacebookId(facebookId))
|
||||
{
|
||||
GAState.SetFacebookId(facebookId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetGender(EGAGender gender)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setGender", () =>
|
||||
{
|
||||
if (GAValidator.ValidateGender(gender))
|
||||
{
|
||||
GAState.SetGender(gender);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void SetBirthYear(int birthYear)
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("setBirthYear", () =>
|
||||
{
|
||||
if (GAValidator.ValidateBirthyear(birthYear))
|
||||
{
|
||||
GAState.SetBirthYear(birthYear);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion // SET STATE CHANGES WHILE RUNNING
|
||||
|
||||
public static void StartSession()
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GAThreading.PerformTaskOnGAThread("startSession", () =>
|
||||
{
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
if(GAState.UseManualSessionHandling)
|
||||
#endif
|
||||
{
|
||||
if(!GAState.Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(GAState.IsEnabled() && GAState.SessionIsStarted())
|
||||
{
|
||||
GAState.EndSessionAndStopQueue(false);
|
||||
}
|
||||
|
||||
GAState.ResumeSessionAndStartQueue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void EndSession()
|
||||
{
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
if(GAState.UseManualSessionHandling)
|
||||
#endif
|
||||
{
|
||||
OnSuspend();
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnResume()
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GALogger.D("OnResume() called");
|
||||
GAThreading.PerformTaskOnGAThread("onResume", () =>
|
||||
{
|
||||
GAState.ResumeSessionAndStartQueue();
|
||||
});
|
||||
}
|
||||
|
||||
public static void OnSuspend()
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GALogger.D("OnSuspend() called");
|
||||
GAThreading.PerformTaskOnGAThread("onSuspend", () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
GAState.EndSessionAndStopQueue(false);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void OnQuit()
|
||||
{
|
||||
if(_endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GALogger.D("OnQuit() called");
|
||||
GAThreading.PerformTaskOnGAThread("onQuit", () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_endThread = true;
|
||||
GAState.EndSessionAndStopQueue(true);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#region COMMAND CENTER
|
||||
|
||||
public static string GetCommandCenterValueAsString(string key, string defaultValue = null)
|
||||
{
|
||||
return GAState.GetConfigurationStringValue(key, defaultValue);
|
||||
}
|
||||
|
||||
public static bool IsCommandCenterReady()
|
||||
{
|
||||
return GAState.IsCommandCenterReady();
|
||||
}
|
||||
|
||||
public static void AddCommandCenterListener(ICommandCenterListener listener)
|
||||
{
|
||||
GAState.AddCommandCenterListener(listener);
|
||||
}
|
||||
|
||||
public static void RemoveCommandCenterListener(ICommandCenterListener listener)
|
||||
{
|
||||
GAState.RemoveCommandCenterListener(listener);
|
||||
}
|
||||
|
||||
public static string GetConfigurationsAsString()
|
||||
{
|
||||
return GAState.GetConfigurationsAsString();
|
||||
}
|
||||
|
||||
#endregion // COMMAND CENTER
|
||||
|
||||
#region PRIVATE HELPERS
|
||||
|
||||
private static bool IsSdkReady(bool needsInitialized)
|
||||
{
|
||||
return IsSdkReady(needsInitialized, true);
|
||||
}
|
||||
|
||||
private static bool IsSdkReady(bool needsInitialized, bool warn)
|
||||
{
|
||||
return IsSdkReady(needsInitialized, warn, "");
|
||||
}
|
||||
|
||||
private static bool IsSdkReady(bool needsInitialized, bool warn, String message)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
message = message + ": ";
|
||||
}
|
||||
|
||||
// Make sure database is ready
|
||||
if (!GAStore.IsTableReady)
|
||||
{
|
||||
if (warn)
|
||||
{
|
||||
GALogger.W(message + "Datastore not initialized");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Is SDK initialized
|
||||
if (needsInitialized && !GAState.Initialized)
|
||||
{
|
||||
if (warn)
|
||||
{
|
||||
GALogger.W(message + "SDK is not initialized");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Is SDK enabled
|
||||
if (needsInitialized && !GAState.IsEnabled())
|
||||
{
|
||||
if (warn)
|
||||
{
|
||||
GALogger.W(message + "SDK is disabled");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Is session started
|
||||
if (needsInitialized && !GAState.SessionIsStarted())
|
||||
{
|
||||
if (warn)
|
||||
{
|
||||
GALogger.W(message + "Session has not started yet");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion // PRIVATE HELPERS
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace GameAnalyticsSDK.Net.Http
|
||||
{
|
||||
internal enum EGAHTTPApiResponse
|
||||
{
|
||||
// client
|
||||
NoResponse,
|
||||
BadResponse,
|
||||
RequestTimeout, // 408
|
||||
JsonEncodeFailed,
|
||||
JsonDecodeFailed,
|
||||
// server
|
||||
InternalServerError,
|
||||
BadRequest, // 400
|
||||
Unauthorized, // 401
|
||||
UnknownResponseCode,
|
||||
Ok
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GameAnalyticsSDK.Net.Http
|
||||
{
|
||||
internal enum EGASdkErrorType
|
||||
{
|
||||
Undefined = 0,
|
||||
Rejected = 1
|
||||
}
|
||||
}
|
||||
|
||||
553
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Http/GAHTTPApi.cs
Normal file
553
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Http/GAHTTPApi.cs
Normal file
@@ -0,0 +1,553 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using GameAnalyticsSDK.Net.Logging;
|
||||
using GameAnalyticsSDK.Net.Utilities;
|
||||
using GameAnalyticsSDK.Net.State;
|
||||
using GameAnalyticsSDK.Net.Validators;
|
||||
using System.Collections.Generic;
|
||||
using GameAnalyticsSDK.Net.Tasks;
|
||||
#if !WINDOWS_UWP && !WINDOWS_WSA
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Net.Security;
|
||||
#endif
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Networking.Connectivity;
|
||||
using GameAnalyticsSDK.Net.Device;
|
||||
#endif
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Http
|
||||
{
|
||||
internal class GAHTTPApi
|
||||
{
|
||||
#region Fields and properties
|
||||
|
||||
private static readonly GAHTTPApi _instance = new GAHTTPApi();
|
||||
|
||||
// base url settings
|
||||
private static string protocol = "https";
|
||||
private static string hostName = "api.gameanalytics.com";
|
||||
private static string version = "v2";
|
||||
private static string baseUrl = getBaseUrl();
|
||||
private static string initializeUrlPath = "init";
|
||||
private static string eventsUrlPath = "events";
|
||||
private bool useGzip;
|
||||
|
||||
private static string getBaseUrl()
|
||||
{
|
||||
return protocol + "://" + hostName + "/" + version;
|
||||
}
|
||||
|
||||
public static GAHTTPApi Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // Fields and properties
|
||||
|
||||
// Constructor - setup the basic information for HTTP
|
||||
private GAHTTPApi()
|
||||
{
|
||||
this.useGzip = true;
|
||||
#if DEBUG
|
||||
this.useGzip = false;
|
||||
#endif
|
||||
#if !WINDOWS_UWP && !WINDOWS_WSA
|
||||
ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback;
|
||||
#endif
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
NetworkInformation.NetworkStatusChanged += NetworkInformationOnNetworkStatusChanged;
|
||||
CheckInternetAccess();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
private static void NetworkInformationOnNetworkStatusChanged(object sender)
|
||||
{
|
||||
CheckInternetAccess();
|
||||
}
|
||||
|
||||
private static void CheckInternetAccess()
|
||||
{
|
||||
var connectionProfile = NetworkInformation.GetInternetConnectionProfile();
|
||||
bool hasInternetAccess = (connectionProfile != null && connectionProfile.GetNetworkConnectivityLevel() == NetworkConnectivityLevel.InternetAccess);
|
||||
|
||||
if (hasInternetAccess)
|
||||
{
|
||||
if (connectionProfile.IsWlanConnectionProfile)
|
||||
{
|
||||
GADevice.ConnectionType = "wifi";
|
||||
}
|
||||
else if (connectionProfile.IsWwanConnectionProfile)
|
||||
{
|
||||
GADevice.ConnectionType = "wwan";
|
||||
}
|
||||
else
|
||||
{
|
||||
GADevice.ConnectionType = "lan";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GADevice.ConnectionType = "offline";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !WINDOWS_UWP && !WINDOWS_WSA
|
||||
private bool MyRemoteCertificateValidationCallback(System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
bool isOk = true;
|
||||
// If there are errors in the certificate chain, look at each error to determine the cause.
|
||||
if (sslPolicyErrors != SslPolicyErrors.None)
|
||||
{
|
||||
for (int i = 0; i < chain.ChainStatus.Length; i++)
|
||||
{
|
||||
if (chain.ChainStatus[i].Status != X509ChainStatusFlags.RevocationStatusUnknown)
|
||||
{
|
||||
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
|
||||
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
|
||||
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0);
|
||||
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
|
||||
bool chainIsValid = chain.Build((X509Certificate2)certificate);
|
||||
if (!chainIsValid)
|
||||
{
|
||||
isOk = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return isOk;
|
||||
}
|
||||
#endif
|
||||
|
||||
#region Public methods
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
public async Task<KeyValuePair<EGAHTTPApiResponse, JSONObject>> RequestInitReturningDict()
|
||||
#else
|
||||
public KeyValuePair<EGAHTTPApiResponse, JSONObject> RequestInitReturningDict()
|
||||
#endif
|
||||
{
|
||||
JSONObject json;
|
||||
EGAHTTPApiResponse result = EGAHTTPApiResponse.NoResponse;
|
||||
string gameKey = GAState.GameKey;
|
||||
|
||||
// Generate URL
|
||||
string url = baseUrl + "/" + gameKey + "/" + initializeUrlPath;
|
||||
url = "https://rubick.gameanalytics.com/v2/command_center?game_key=" + gameKey + "&interval_seconds=1000000";
|
||||
//url = "https://requestb.in/1fvbe2g1";
|
||||
|
||||
GALogger.D("Sending 'init' URL: " + url);
|
||||
|
||||
JSONObject initAnnotations = GAState.GetInitAnnotations();
|
||||
|
||||
// make JSON string from data
|
||||
string JSONstring = initAnnotations.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(JSONstring))
|
||||
{
|
||||
result = EGAHTTPApiResponse.JsonEncodeFailed;
|
||||
json = null;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONObject>(result, json);
|
||||
}
|
||||
|
||||
string body = "";
|
||||
HttpStatusCode responseCode = (HttpStatusCode)0;
|
||||
string responseDescription = "";
|
||||
string authorization = "";
|
||||
try
|
||||
{
|
||||
byte[] payloadData = CreatePayloadData(JSONstring, false);
|
||||
HttpWebRequest request = CreateRequest(url, payloadData, false);
|
||||
authorization = request.Headers[HttpRequestHeader.Authorization];
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using (Stream dataStream = await request.GetRequestStreamAsync())
|
||||
#else
|
||||
using(Stream dataStream = request.GetRequestStream())
|
||||
#endif
|
||||
{
|
||||
dataStream.Write(payloadData, 0, payloadData.Length);
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using (HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse)
|
||||
#else
|
||||
using(HttpWebResponse response = request.GetResponse() as HttpWebResponse)
|
||||
#endif
|
||||
{
|
||||
using (Stream dataStream = response.GetResponseStream())
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(dataStream))
|
||||
{
|
||||
string responseString = reader.ReadToEnd();
|
||||
|
||||
responseCode = response.StatusCode;
|
||||
responseDescription = response.StatusDescription;
|
||||
|
||||
// print result
|
||||
body = responseString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
if(e.Response != null)
|
||||
{
|
||||
using (HttpWebResponse response = (HttpWebResponse)e.Response)
|
||||
{
|
||||
using (Stream streamResponse = response.GetResponseStream())
|
||||
{
|
||||
using (StreamReader streamRead = new StreamReader(streamResponse))
|
||||
{
|
||||
string responseString = streamRead.ReadToEnd();
|
||||
|
||||
responseCode = response.StatusCode;
|
||||
responseDescription = response.StatusDescription;
|
||||
|
||||
body = responseString;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
GALogger.E(e.ToString());
|
||||
}
|
||||
|
||||
// process the response
|
||||
GALogger.D("init request content : " + body);
|
||||
|
||||
JSONNode requestJsonDict = JSON.Parse(body);
|
||||
EGAHTTPApiResponse requestResponseEnum = ProcessRequestResponse(responseCode, responseDescription, body, "Init");
|
||||
|
||||
// if not 200 result
|
||||
if (requestResponseEnum != EGAHTTPApiResponse.Ok && requestResponseEnum != EGAHTTPApiResponse.BadRequest)
|
||||
{
|
||||
GALogger.D("Failed Init Call. URL: " + url + ", Authorization: " + authorization + ", JSONString: " + JSONstring);
|
||||
result = requestResponseEnum;
|
||||
json = null;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONObject>(result, json);
|
||||
}
|
||||
|
||||
if (requestJsonDict == null)
|
||||
{
|
||||
GALogger.D("Failed Init Call. Json decoding failed");
|
||||
result = EGAHTTPApiResponse.JsonDecodeFailed;
|
||||
json = null;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONObject>(result, json);
|
||||
}
|
||||
|
||||
// print reason if bad request
|
||||
if (requestResponseEnum == EGAHTTPApiResponse.BadRequest)
|
||||
{
|
||||
GALogger.D("Failed Init Call. Bad request. Response: " + requestJsonDict.ToString());
|
||||
// return bad request result
|
||||
result = requestResponseEnum;
|
||||
json = null;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONObject>(result, json);
|
||||
}
|
||||
|
||||
// validate Init call values
|
||||
JSONObject validatedInitValues = GAValidator.ValidateAndCleanInitRequestResponse(requestJsonDict);
|
||||
|
||||
if (validatedInitValues == null)
|
||||
{
|
||||
result = EGAHTTPApiResponse.BadResponse;
|
||||
json = null;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONObject>(result, json);
|
||||
}
|
||||
|
||||
// all ok
|
||||
result = EGAHTTPApiResponse.Ok;
|
||||
json = validatedInitValues;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONObject>(result, json);
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
public async Task<KeyValuePair<EGAHTTPApiResponse, JSONNode>> SendEventsInArray(List<JSONNode> eventArray)
|
||||
#else
|
||||
public KeyValuePair<EGAHTTPApiResponse, JSONNode> SendEventsInArray(List<JSONNode> eventArray)
|
||||
#endif
|
||||
{
|
||||
JSONNode json;
|
||||
|
||||
if (eventArray.Count == 0)
|
||||
{
|
||||
GALogger.D("sendEventsInArray called with missing eventArray");
|
||||
}
|
||||
|
||||
EGAHTTPApiResponse result = EGAHTTPApiResponse.NoResponse;
|
||||
string gameKey = GAState.GameKey;
|
||||
|
||||
// Generate URL
|
||||
string url = baseUrl + "/" + gameKey + "/" + eventsUrlPath;
|
||||
GALogger.D("Sending 'events' URL: " + url);
|
||||
|
||||
// make JSON string from data
|
||||
string JSONstring = GAUtilities.ArrayOfObjectsToJsonString(eventArray);
|
||||
|
||||
if (JSONstring.Length == 0)
|
||||
{
|
||||
GALogger.D("sendEventsInArray JSON encoding failed of eventArray");
|
||||
json = null;
|
||||
result = EGAHTTPApiResponse.JsonEncodeFailed;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONNode>(result, json);
|
||||
}
|
||||
|
||||
string body = "";
|
||||
HttpStatusCode responseCode = (HttpStatusCode)0;
|
||||
string responseDescription = "";
|
||||
string authorization = "";
|
||||
try
|
||||
{
|
||||
byte[] payloadData = CreatePayloadData(JSONstring, useGzip);
|
||||
HttpWebRequest request = CreateRequest(url, payloadData, useGzip);
|
||||
authorization = request.Headers[HttpRequestHeader.Authorization];
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using (Stream dataStream = await request.GetRequestStreamAsync())
|
||||
#else
|
||||
using(Stream dataStream = request.GetRequestStream())
|
||||
#endif
|
||||
{
|
||||
dataStream.Write(payloadData, 0, payloadData.Length);
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using (HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse)
|
||||
#else
|
||||
using(HttpWebResponse response = request.GetResponse() as HttpWebResponse)
|
||||
#endif
|
||||
{
|
||||
using (Stream dataStream = response.GetResponseStream())
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(dataStream))
|
||||
{
|
||||
string responseString = reader.ReadToEnd();
|
||||
|
||||
responseCode = response.StatusCode;
|
||||
responseDescription = response.StatusDescription;
|
||||
|
||||
// print result
|
||||
body = responseString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
if(e.Response != null)
|
||||
{
|
||||
using (HttpWebResponse response = (HttpWebResponse)e.Response)
|
||||
{
|
||||
using (Stream streamResponse = response.GetResponseStream())
|
||||
{
|
||||
using (StreamReader streamRead = new StreamReader(streamResponse))
|
||||
{
|
||||
string responseString = streamRead.ReadToEnd();
|
||||
|
||||
responseCode = response.StatusCode;
|
||||
responseDescription = response.StatusDescription;
|
||||
|
||||
body = responseString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
GALogger.E(e.ToString());
|
||||
}
|
||||
|
||||
GALogger.D("events request content: " + body);
|
||||
|
||||
EGAHTTPApiResponse requestResponseEnum = ProcessRequestResponse(responseCode, responseDescription, body, "Events");
|
||||
|
||||
// if not 200 result
|
||||
if (requestResponseEnum != EGAHTTPApiResponse.Ok && requestResponseEnum != EGAHTTPApiResponse.BadRequest)
|
||||
{
|
||||
GALogger.D("Failed events Call. URL: " + url + ", Authorization: " + authorization + ", JSONString: " + JSONstring);
|
||||
json = null;
|
||||
result = requestResponseEnum;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONNode>(result, json);
|
||||
}
|
||||
|
||||
// decode JSON
|
||||
JSONNode requestJsonDict = JSON.Parse(body);
|
||||
|
||||
if (requestJsonDict == null)
|
||||
{
|
||||
json = null;
|
||||
result = EGAHTTPApiResponse.JsonDecodeFailed;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONNode>(result, json);
|
||||
}
|
||||
|
||||
// print reason if bad request
|
||||
if (requestResponseEnum == EGAHTTPApiResponse.BadRequest)
|
||||
{
|
||||
GALogger.D("Failed Events Call. Bad request. Response: " + requestJsonDict.ToString());
|
||||
}
|
||||
|
||||
// return response
|
||||
json = requestJsonDict;
|
||||
result = requestResponseEnum;
|
||||
return new KeyValuePair<EGAHTTPApiResponse, JSONNode>(result, json);
|
||||
}
|
||||
|
||||
public void SendSdkErrorEvent(EGASdkErrorType type)
|
||||
{
|
||||
if(!GAState.IsEventSubmissionEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string gameKey = GAState.GameKey;
|
||||
string secretKey = GAState.GameSecret;
|
||||
|
||||
// Validate
|
||||
if (!GAValidator.ValidateSdkErrorEvent(gameKey, secretKey, type))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate URL
|
||||
string url = baseUrl + "/" + gameKey + "/" + eventsUrlPath;
|
||||
GALogger.D("Sending 'events' URL: " + url);
|
||||
|
||||
string payloadJSONString = "";
|
||||
|
||||
JSONObject json = GAState.GetSdkErrorEventAnnotations();
|
||||
|
||||
string typeString = SdkErrorTypeToString(type);
|
||||
json.Add("type", typeString);
|
||||
|
||||
List<JSONNode> eventArray = new List<JSONNode>();
|
||||
eventArray.Add(json);
|
||||
payloadJSONString = GAUtilities.ArrayOfObjectsToJsonString(eventArray);
|
||||
|
||||
if(string.IsNullOrEmpty(payloadJSONString))
|
||||
{
|
||||
GALogger.W("sendSdkErrorEvent: JSON encoding failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
GALogger.D("sendSdkErrorEvent json: " + payloadJSONString);
|
||||
byte[] payloadData = Encoding.UTF8.GetBytes(payloadJSONString);
|
||||
SdkErrorTask sdkErrorTask = new SdkErrorTask(type, payloadData, secretKey);
|
||||
sdkErrorTask.Execute(url);
|
||||
}
|
||||
|
||||
#endregion // Public methods
|
||||
|
||||
#region Private methods
|
||||
|
||||
private byte[] CreatePayloadData(string payload, bool gzip)
|
||||
{
|
||||
byte[] payloadData;
|
||||
|
||||
if(gzip)
|
||||
{
|
||||
payloadData = GAUtilities.GzipCompress(payload);
|
||||
GALogger.D("Gzip stats. Size: " + Encoding.UTF8.GetBytes(payload).Length + ", Compressed: " + payloadData.Length + ", Content: " + payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
payloadData = Encoding.UTF8.GetBytes(payload);
|
||||
}
|
||||
|
||||
return payloadData;
|
||||
}
|
||||
|
||||
private static string SdkErrorTypeToString(EGASdkErrorType value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case EGASdkErrorType.Rejected:
|
||||
{
|
||||
return "rejected";
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HttpWebRequest CreateRequest(string url, byte[] payloadData, bool gzip)
|
||||
{
|
||||
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
|
||||
request.Method = "POST";
|
||||
#if WINDOWS_UWP
|
||||
request.Headers[HttpRequestHeader.ContentLength] = payloadData.Length.ToString();
|
||||
#elif WINDOWS_WSA
|
||||
//request.Headers[HttpRequestHeader.ContentLength] = payloadData.Length.ToString();
|
||||
// Bug setting Content Length on WSA
|
||||
#else
|
||||
request.ContentLength = payloadData.Length;
|
||||
#endif
|
||||
|
||||
if (gzip)
|
||||
{
|
||||
request.Headers[HttpRequestHeader.ContentEncoding] = "gzip";
|
||||
}
|
||||
|
||||
// create authorization hash
|
||||
String key = GAState.GameSecret;
|
||||
|
||||
request.Headers[HttpRequestHeader.Authorization] = GAUtilities.HmacWithKey(key, payloadData);
|
||||
request.ContentType = "application/json";
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private EGAHTTPApiResponse ProcessRequestResponse(HttpStatusCode responseCode, string responseMessage, string body, string requestId)
|
||||
{
|
||||
// if no result - often no connection
|
||||
if(string.IsNullOrEmpty(body))
|
||||
{
|
||||
GALogger.D(requestId + " request. failed. Might be no connection. Description: " + responseMessage + ", Status code: " + responseCode);
|
||||
return EGAHTTPApiResponse.NoResponse;
|
||||
}
|
||||
|
||||
// ok
|
||||
if (responseCode == HttpStatusCode.OK)
|
||||
{
|
||||
return EGAHTTPApiResponse.Ok;
|
||||
}
|
||||
|
||||
// 401 can return 0 status
|
||||
if (responseCode == (HttpStatusCode)0 || responseCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
GALogger.D(requestId + " request. 401 - Unauthorized.");
|
||||
return EGAHTTPApiResponse.Unauthorized;
|
||||
}
|
||||
|
||||
if (responseCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
GALogger.D(requestId + " request. 400 - Bad Request.");
|
||||
return EGAHTTPApiResponse.BadRequest;
|
||||
}
|
||||
|
||||
if (responseCode == HttpStatusCode.InternalServerError)
|
||||
{
|
||||
GALogger.D(requestId + " request. 500 - Internal Server Error.");
|
||||
return EGAHTTPApiResponse.InternalServerError;
|
||||
}
|
||||
|
||||
return EGAHTTPApiResponse.UnknownResponseCode;
|
||||
}
|
||||
|
||||
#endregion // Private methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace GameAnalyticsSDK.Net
|
||||
{
|
||||
public interface ICommandCenterListener
|
||||
{
|
||||
void OnCommandCenterUpdated();
|
||||
}
|
||||
}
|
||||
233
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Logging/GALogger.cs
Normal file
233
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Logging/GALogger.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
using System;
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using Windows.Foundation.Diagnostics;
|
||||
using MetroLog;
|
||||
using MetroLog.Targets;
|
||||
#elif MONO
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
using GameAnalyticsSDK.Net.Device;
|
||||
using System.IO;
|
||||
#endif
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Logging
|
||||
{
|
||||
internal class GALogger
|
||||
{
|
||||
#region Fields and properties
|
||||
|
||||
private static readonly GALogger _instance = new GALogger();
|
||||
private bool infoLogEnabled;
|
||||
private bool infoLogVerboseEnabled;
|
||||
#pragma warning disable 0649
|
||||
private static bool debugEnabled;
|
||||
#pragma warning restore 0649
|
||||
private const string Tag = "GameAnalytics";
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
private IFileLoggingSession session;
|
||||
private ILoggingChannel logger;
|
||||
private ILogger log;
|
||||
#elif MONO
|
||||
private static ILogger logger;
|
||||
#elif !UNITY
|
||||
private ILogger logger;
|
||||
#endif
|
||||
|
||||
private static GALogger Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool InfoLog
|
||||
{
|
||||
set
|
||||
{
|
||||
Instance.infoLogEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool VerboseLog
|
||||
{
|
||||
set
|
||||
{
|
||||
Instance.infoLogVerboseEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // Fields and properties
|
||||
|
||||
private GALogger()
|
||||
{
|
||||
#if DEBUG
|
||||
debugEnabled = true;
|
||||
#endif
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
session = new FileLoggingSession("ga-session");
|
||||
#if WINDOWS_UWP
|
||||
var options = new LoggingChannelOptions();
|
||||
logger = new LoggingChannel("ga-channel", options);
|
||||
#else
|
||||
logger = new LoggingChannel("ga-channel");
|
||||
#endif
|
||||
session.AddLoggingChannel(logger);
|
||||
|
||||
LogManagerFactory.DefaultConfiguration.AddTarget(LogLevel.Trace, LogLevel.Fatal, new StreamingFileTarget());
|
||||
log = LogManagerFactory.DefaultLogManager.GetLogger<GALogger>();
|
||||
#elif MONO
|
||||
logger = LogManager.GetCurrentClassLogger();
|
||||
var config = new LoggingConfiguration();
|
||||
|
||||
var consoleTarget = new ColoredConsoleTarget();
|
||||
config.AddTarget("console", consoleTarget);
|
||||
|
||||
var fileTarget = new FileTarget();
|
||||
config.AddTarget("file", fileTarget);
|
||||
|
||||
//consoleTarget.Layout = @"${date:format=HH\:mm\:ss} ${logger} ${message}";
|
||||
fileTarget.FileName = GADevice.WritablePath + Path.DirectorySeparatorChar + "ga_log.txt";
|
||||
fileTarget.Layout = "${message}";
|
||||
|
||||
var rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget);
|
||||
config.LoggingRules.Add(rule1);
|
||||
|
||||
var rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget);
|
||||
config.LoggingRules.Add(rule2);
|
||||
|
||||
LogManager.Configuration = config;
|
||||
#endif
|
||||
}
|
||||
|
||||
#region Public methods
|
||||
|
||||
public static void I(String format)
|
||||
{
|
||||
if(!Instance.infoLogEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string message = "Info/" + Tag + ": " + format;
|
||||
Instance.SendNotificationMessage(message, EGALoggerMessageType.Info);
|
||||
}
|
||||
|
||||
public static void W(String format)
|
||||
{
|
||||
string message = "Warning/" + Tag + ": " + format;
|
||||
Instance.SendNotificationMessage(message, EGALoggerMessageType.Warning);
|
||||
}
|
||||
|
||||
public static void E(String format)
|
||||
{
|
||||
string message = "Error/" + Tag + ": " + format;
|
||||
Instance.SendNotificationMessage(message, EGALoggerMessageType.Error);
|
||||
}
|
||||
|
||||
public static void II(String format)
|
||||
{
|
||||
if(!Instance.infoLogVerboseEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string message = "Verbose/" + Tag + ": " + format;
|
||||
Instance.SendNotificationMessage(message, EGALoggerMessageType.Info);
|
||||
}
|
||||
|
||||
public static void D(String format)
|
||||
{
|
||||
if(!debugEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string message = "Debug/" + Tag + ": " + format;
|
||||
Instance.SendNotificationMessage(message, EGALoggerMessageType.Debug);
|
||||
}
|
||||
|
||||
#endregion // Public methods
|
||||
|
||||
#region Private methods
|
||||
|
||||
private void SendNotificationMessage(string message, EGALoggerMessageType type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case EGALoggerMessageType.Error:
|
||||
{
|
||||
#if UNITY
|
||||
UnityEngine.Debug.LogError(message);
|
||||
#elif WINDOWS_UWP || WINDOWS_WSA
|
||||
logger.LogMessage(message, LoggingLevel.Error);
|
||||
log.Error(message);
|
||||
GameAnalytics.MessageLogged(message, type);
|
||||
#elif MONO
|
||||
logger.Error(message);
|
||||
#else
|
||||
logger.LogError(message);
|
||||
GameAnalytics.MessageLogged(message, type);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case EGALoggerMessageType.Warning:
|
||||
{
|
||||
#if UNITY
|
||||
UnityEngine.Debug.LogWarning(message);
|
||||
#elif WINDOWS_UWP || WINDOWS_WSA
|
||||
logger.LogMessage(message, LoggingLevel.Warning);
|
||||
log.Warn(message);
|
||||
GameAnalytics.MessageLogged(message, type);
|
||||
#elif MONO
|
||||
logger.Warn(message);
|
||||
#else
|
||||
logger.LogWarning(message);
|
||||
GameAnalytics.MessageLogged(message, type);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case EGALoggerMessageType.Debug:
|
||||
{
|
||||
#if UNITY
|
||||
UnityEngine.Debug.Log(message);
|
||||
#elif WINDOWS_UWP || WINDOWS_WSA
|
||||
logger.LogMessage(message, LoggingLevel.Information);
|
||||
log.Debug(message);
|
||||
GameAnalytics.MessageLogged(message, type);
|
||||
#elif MONO
|
||||
logger.Debug(message);
|
||||
#else
|
||||
logger.LogDebug(message);
|
||||
GameAnalytics.MessageLogged(message, type);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case EGALoggerMessageType.Info:
|
||||
{
|
||||
#if UNITY
|
||||
UnityEngine.Debug.Log(message);
|
||||
#elif WINDOWS_UWP || WINDOWS_WSA
|
||||
logger.LogMessage(message, LoggingLevel.Information);
|
||||
log.Info(message);
|
||||
GameAnalytics.MessageLogged(message, type);
|
||||
#elif MONO
|
||||
logger.Info(message);
|
||||
#else
|
||||
logger.LogInformation(message);
|
||||
GameAnalytics.MessageLogged(message, type);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // Private methods
|
||||
}
|
||||
}
|
||||
|
||||
1341
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/State/GAState.cs
Normal file
1341
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/State/GAState.cs
Normal file
File diff suppressed because it is too large
Load Diff
478
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Store/GAStore.cs
Normal file
478
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Store/GAStore.cs
Normal file
@@ -0,0 +1,478 @@
|
||||
using System;
|
||||
#if MONO
|
||||
using SqliteConnection = System.Data.SQLite.SQLiteConnection;
|
||||
using SqliteTransaction = System.Data.SQLite.SQLiteTransaction;
|
||||
using SqliteCommand = System.Data.SQLite.SQLiteCommand;
|
||||
using SqliteDataReader = System.Data.SQLite.SQLiteDataReader;
|
||||
using SqliteException = System.Data.SQLite.SQLiteException;
|
||||
using SqliteConnectionStringBuilder = System.Data.SQLite.SQLiteConnectionStringBuilder;
|
||||
#elif WINDOWS_WSA || !UNITY || WINDOWS_UWP
|
||||
using Microsoft.Data.Sqlite;
|
||||
using System.Reflection;
|
||||
#else
|
||||
using Mono.Data.Sqlite;
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
using GameAnalyticsSDK.Net.Utilities;
|
||||
using GameAnalyticsSDK.Net.Logging;
|
||||
using GameAnalyticsSDK.Net.Device;
|
||||
using System.IO;
|
||||
#if UNITY_SAMSUNGTV
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
#if WINDOWS_WSA
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Store
|
||||
{
|
||||
internal class GAStore
|
||||
{
|
||||
#region Fields and properties
|
||||
|
||||
#if UNITY_SAMSUNGTV
|
||||
[DllImport("__Internal")]
|
||||
private static extern long sqlite3_memory_used();
|
||||
#endif
|
||||
|
||||
#if UNITY_SAMSUNGTV
|
||||
public const bool InMemory = true;
|
||||
private const long MaxDbSizeBytes = 2097152;
|
||||
private const long MaxDbSizeBytesBeforeTrim = 2621440;
|
||||
#else
|
||||
public const bool InMemory = false;
|
||||
private const long MaxDbSizeBytes = 6291456;
|
||||
private const long MaxDbSizeBytesBeforeTrim = 5242880;
|
||||
#endif
|
||||
|
||||
private static readonly GAStore _instance = new GAStore();
|
||||
private static GAStore Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
// set when calling "ensureDatabase"
|
||||
// using a "writablePath" that needs to be set into the C++ component before
|
||||
private string dbPath = "";
|
||||
|
||||
// local pointer to database
|
||||
private SqliteConnection SqlDatabase
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool DbReady
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool _tableReady;
|
||||
public static bool IsTableReady
|
||||
{
|
||||
get { return Instance._tableReady; }
|
||||
private set { Instance._tableReady = value; }
|
||||
}
|
||||
|
||||
public static bool IsDbTooLargeForEvents
|
||||
{
|
||||
get { return DbSizeBytes > MaxDbSizeBytes; }
|
||||
}
|
||||
|
||||
#endregion // Fields and properties
|
||||
|
||||
private GAStore()
|
||||
{
|
||||
}
|
||||
|
||||
#region Public methods
|
||||
|
||||
public static JSONArray ExecuteQuerySync(string sql)
|
||||
{
|
||||
return ExecuteQuerySync(sql, new Dictionary<string, object>());
|
||||
}
|
||||
|
||||
public static JSONArray ExecuteQuerySync(string sql, Dictionary<string, object> parameters)
|
||||
{
|
||||
return ExecuteQuerySync(sql, parameters, false);
|
||||
}
|
||||
|
||||
public static JSONArray ExecuteQuerySync(string sql, Dictionary<string, object> parameters, bool useTransaction)
|
||||
{
|
||||
// Force transaction if it is an update, insert or delete.
|
||||
if (GAUtilities.StringMatch(sql.ToUpperInvariant(), "^(UPDATE|INSERT|DELETE)"))
|
||||
{
|
||||
useTransaction = true;
|
||||
}
|
||||
|
||||
// Get database connection from singelton sharedInstance
|
||||
SqliteConnection sqlDatabasePtr = Instance.SqlDatabase;
|
||||
|
||||
// Create mutable array for results
|
||||
JSONArray results = new JSONArray();
|
||||
|
||||
SqliteTransaction transaction = null;
|
||||
SqliteCommand command = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (useTransaction)
|
||||
{
|
||||
transaction = sqlDatabasePtr.BeginTransaction();
|
||||
}
|
||||
|
||||
command = sqlDatabasePtr.CreateCommand();
|
||||
|
||||
if (useTransaction)
|
||||
{
|
||||
command.Transaction = transaction;
|
||||
}
|
||||
command.CommandText = sql;
|
||||
command.Prepare();
|
||||
|
||||
// Bind parameters
|
||||
if (parameters.Count != 0)
|
||||
{
|
||||
foreach(KeyValuePair<string, object> pair in parameters)
|
||||
{
|
||||
command.Parameters.AddWithValue(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
using (SqliteDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
// Loop through results
|
||||
while (reader.Read())
|
||||
{
|
||||
// get columns count
|
||||
int columnCount = reader.FieldCount;
|
||||
|
||||
JSONObject row = new JSONObject();
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
{
|
||||
string column = reader.GetName(i);
|
||||
|
||||
if(string.IsNullOrEmpty(column))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
row[column] = reader.GetValue(i).ToString();
|
||||
}
|
||||
results.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
if (useTransaction)
|
||||
{
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
catch (SqliteException e)
|
||||
{
|
||||
// TODO(nikolaj): Should we do a db validation to see if the db is corrupt here?
|
||||
GALogger.E("SQLITE3 ERROR: " + e);
|
||||
results = null;
|
||||
|
||||
if (useTransaction)
|
||||
{
|
||||
if(transaction != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
transaction.Rollback();
|
||||
}
|
||||
catch (SqliteException ex)
|
||||
{
|
||||
GALogger.E("SQLITE3 ROLLBACK ERROR: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
transaction.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(command != null)
|
||||
{
|
||||
command.Dispose();
|
||||
}
|
||||
|
||||
if(transaction != null)
|
||||
{
|
||||
transaction.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Return results
|
||||
return results;
|
||||
}
|
||||
|
||||
public static bool EnsureDatabase(bool dropDatabase, string key)
|
||||
{
|
||||
// lazy creation of db path
|
||||
if(string.IsNullOrEmpty(Instance.dbPath))
|
||||
{
|
||||
// initialize db path
|
||||
#pragma warning disable 0429
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
Instance.dbPath = InMemory ? ":memory:" : Path.Combine(GADevice.WritablePath, "ga.sqlite3");
|
||||
#else
|
||||
Instance.dbPath = InMemory ? ":memory:" : Path.Combine(Path.Combine(GADevice.WritablePath, key), "ga.sqlite3");
|
||||
|
||||
if (!InMemory)
|
||||
{
|
||||
|
||||
string d = Path.Combine(GADevice.WritablePath, key);
|
||||
if (!Directory.Exists(d))
|
||||
{
|
||||
Directory.CreateDirectory(d);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#pragma warning restore 0429
|
||||
GALogger.D("Database path set to: " + Instance.dbPath);
|
||||
}
|
||||
|
||||
// Open database
|
||||
try
|
||||
{
|
||||
#if UNITY
|
||||
Instance.SqlDatabase = new SqliteConnection("URI=file:" + Instance.dbPath + ";Version=3");
|
||||
#else
|
||||
Instance.SqlDatabase = new SqliteConnection(new SqliteConnectionStringBuilder
|
||||
{
|
||||
DataSource = Instance.dbPath
|
||||
}.ConnectionString);
|
||||
#endif
|
||||
|
||||
Instance.SqlDatabase.Open();
|
||||
Instance.DbReady = true;
|
||||
GALogger.I("Database opened: " + Instance.dbPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Instance.DbReady = false;
|
||||
GALogger.W("Could not open database: " + Instance.dbPath + " " + e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dropDatabase)
|
||||
{
|
||||
GALogger.D("Drop tables");
|
||||
ExecuteQuerySync("DROP TABLE ga_events");
|
||||
ExecuteQuerySync("DROP TABLE ga_state");
|
||||
ExecuteQuerySync("DROP TABLE ga_session");
|
||||
ExecuteQuerySync("DROP TABLE ga_progression");
|
||||
ExecuteQuerySync("VACUUM");
|
||||
}
|
||||
|
||||
// Create statements
|
||||
string sql_ga_events = "CREATE TABLE IF NOT EXISTS ga_events(status CHAR(50) NOT NULL, category CHAR(50) NOT NULL, session_id CHAR(50) NOT NULL, client_ts CHAR(50) NOT NULL, event TEXT NOT NULL);";
|
||||
string sql_ga_session = "CREATE TABLE IF NOT EXISTS ga_session(session_id CHAR(50) PRIMARY KEY NOT NULL, timestamp CHAR(50) NOT NULL, event TEXT NOT NULL);";
|
||||
string sql_ga_state = "CREATE TABLE IF NOT EXISTS ga_state(key CHAR(255) PRIMARY KEY NOT NULL, value TEXT);";
|
||||
string sql_ga_progression = "CREATE TABLE IF NOT EXISTS ga_progression(progression CHAR(255) PRIMARY KEY NOT NULL, tries CHAR(255));";
|
||||
|
||||
JSONArray results = ExecuteQuerySync(sql_ga_events);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ExecuteQuerySync("SELECT status FROM ga_events LIMIT 0,1") == null)
|
||||
{
|
||||
GALogger.D("ga_events corrupt, recreating.");
|
||||
ExecuteQuerySync("DROP TABLE ga_events");
|
||||
results = ExecuteQuerySync(sql_ga_events);
|
||||
if (results == null)
|
||||
{
|
||||
GALogger.W("ga_events corrupt, could not recreate it.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
results = ExecuteQuerySync(sql_ga_session);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ExecuteQuerySync("SELECT session_id FROM ga_session LIMIT 0,1") == null)
|
||||
{
|
||||
GALogger.D("ga_session corrupt, recreating.");
|
||||
ExecuteQuerySync("DROP TABLE ga_session");
|
||||
results = ExecuteQuerySync(sql_ga_session);
|
||||
if (results == null)
|
||||
{
|
||||
GALogger.W("ga_session corrupt, could not recreate it.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
results = ExecuteQuerySync(sql_ga_state);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ExecuteQuerySync("SELECT key FROM ga_state LIMIT 0,1") == null)
|
||||
{
|
||||
GALogger.D("ga_state corrupt, recreating.");
|
||||
ExecuteQuerySync("DROP TABLE ga_state");
|
||||
results = ExecuteQuerySync(sql_ga_state);
|
||||
if (results == null)
|
||||
{
|
||||
GALogger.W("ga_state corrupt, could not recreate it.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
results = ExecuteQuerySync(sql_ga_progression);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ExecuteQuerySync("SELECT progression FROM ga_progression LIMIT 0,1") == null)
|
||||
{
|
||||
GALogger.D("ga_progression corrupt, recreating.");
|
||||
ExecuteQuerySync("DROP TABLE ga_progression");
|
||||
results = ExecuteQuerySync(sql_ga_progression);
|
||||
if (results == null)
|
||||
{
|
||||
GALogger.W("ga_progression corrupt, could not recreate it.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// All good
|
||||
TrimEventTable();
|
||||
|
||||
IsTableReady = true;
|
||||
GALogger.D("Database tables ensured present");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma warning disable 0162
|
||||
public static void SetState(string key, string value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
|
||||
if (InMemory)
|
||||
{
|
||||
#if UNITY
|
||||
if(UnityEngine.PlayerPrefs.HasKey(State.GAState.InMemoryPrefix + key))
|
||||
{
|
||||
UnityEngine.PlayerPrefs.DeleteKey(State.GAState.InMemoryPrefix + key);
|
||||
}
|
||||
#else
|
||||
GALogger.W("SetState: No implementation yet for InMemory=true");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> parameterArray = new Dictionary<string, object>();
|
||||
parameterArray.Add("$key", key);
|
||||
ExecuteQuerySync("DELETE FROM ga_state WHERE key = $key;", parameterArray);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (InMemory)
|
||||
{
|
||||
#if UNITY
|
||||
UnityEngine.PlayerPrefs.SetString(State.GAState.InMemoryPrefix + key, value);
|
||||
#else
|
||||
GALogger.W("SetState: No implementation yet for InMemory=true");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, object> parameterArray = new Dictionary<string, object>();
|
||||
parameterArray.Add("$key", key);
|
||||
parameterArray.Add("$value", value);
|
||||
ExecuteQuerySync("INSERT OR REPLACE INTO ga_state (key, value) VALUES($key, $value);", parameterArray, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore 0162
|
||||
|
||||
public static long DbSizeBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
#if WINDOWS_WSA
|
||||
Task<StorageFile> fileTask = Task.Run<StorageFile>(async () => await StorageFile.GetFileFromPathAsync(Instance.dbPath));
|
||||
StorageFile file = fileTask.GetAwaiter().GetResult();
|
||||
Task<BasicProperties> propertiesTask = Task.Run<BasicProperties>(async () => await file.GetBasicPropertiesAsync());
|
||||
BasicProperties properties = propertiesTask.GetAwaiter().GetResult();
|
||||
|
||||
return (long)properties.Size;
|
||||
#elif UNITY_SAMSUNGTV
|
||||
long result = 0;
|
||||
try
|
||||
{
|
||||
result = sqlite3_memory_used();
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
GALogger.W("DbSizeBytes: sqlite3_memory_used failed using DbSizeBytes=0");
|
||||
}
|
||||
|
||||
return result;
|
||||
#else
|
||||
return InMemory ? 0 : new FileInfo(Instance.dbPath).Length;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // Public methods
|
||||
|
||||
#region Private methods
|
||||
|
||||
private static void TrimEventTable()
|
||||
{
|
||||
if (DbSizeBytes > MaxDbSizeBytesBeforeTrim)
|
||||
{
|
||||
JSONArray resultSessionArray = ExecuteQuerySync("SELECT session_id, Max(client_ts) FROM ga_events GROUP BY session_id ORDER BY client_ts LIMIT 3");
|
||||
|
||||
if(resultSessionArray != null && resultSessionArray.Count > 0)
|
||||
{
|
||||
string sessionDeleteString = "";
|
||||
|
||||
for(int i = 0; i < resultSessionArray.Count; ++i)
|
||||
{
|
||||
sessionDeleteString += resultSessionArray[i].Value;
|
||||
|
||||
if(i < resultSessionArray.Count - 1)
|
||||
{
|
||||
sessionDeleteString += ",";
|
||||
}
|
||||
}
|
||||
|
||||
string deleteOldSessionSql = "DELETE FROM ga_events WHERE session_id IN (\"" + sessionDeleteString + "\");";
|
||||
GALogger.W("Database too large when initializing. Deleting the oldest 3 sessions.");
|
||||
ExecuteQuerySync(deleteOldSessionSql);
|
||||
ExecuteQuerySync("VACUUM");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
152
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Tasks/SdkErrorTask.cs
Normal file
152
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Tasks/SdkErrorTask.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using GameAnalyticsSDK.Net.Http;
|
||||
using System.Collections.Generic;
|
||||
using GameAnalyticsSDK.Net.Utilities;
|
||||
using System.Net;
|
||||
using System.IO;
|
||||
using GameAnalyticsSDK.Net.Logging;
|
||||
using Foundation.Tasks;
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Tasks
|
||||
{
|
||||
internal class SdkErrorTask
|
||||
{
|
||||
protected EGASdkErrorType type;
|
||||
protected byte[] payloadData;
|
||||
protected string hashHmac;
|
||||
protected string body = "";
|
||||
private const int MaxCount = 10;
|
||||
private static Dictionary<EGASdkErrorType, int> countMap = new Dictionary<EGASdkErrorType, int>();
|
||||
|
||||
public SdkErrorTask(EGASdkErrorType type, byte[] payloadData, string secretKey)
|
||||
{
|
||||
this.type = type;
|
||||
this.payloadData = payloadData;
|
||||
this.hashHmac = GAUtilities.HmacWithKey(secretKey, payloadData);
|
||||
}
|
||||
|
||||
public void Execute(string url)
|
||||
{
|
||||
AsyncTask.Run(() =>
|
||||
{
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
DoInBackground(url).Wait();
|
||||
#else
|
||||
DoInBackground(url);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
protected async Task DoInBackground(string url)
|
||||
#else
|
||||
protected void DoInBackground(string url)
|
||||
#endif
|
||||
{
|
||||
if(!countMap.ContainsKey(this.type))
|
||||
{
|
||||
countMap.Add(this.type, 0);
|
||||
}
|
||||
|
||||
if(countMap[this.type] >= MaxCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HttpStatusCode responseCode = (HttpStatusCode)0;
|
||||
string responseDescription = "";
|
||||
|
||||
try
|
||||
{
|
||||
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
|
||||
request.Method = "POST";
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
request.Headers[HttpRequestHeader.ContentLength] = this.payloadData.Length.ToString();
|
||||
#else
|
||||
request.ContentLength = payloadData.Length;
|
||||
#endif
|
||||
request.Headers[HttpRequestHeader.Authorization] = this.hashHmac;
|
||||
request.ContentType = "application/json";
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using (Stream dataStream = await request.GetRequestStreamAsync())
|
||||
#else
|
||||
using (Stream dataStream = request.GetRequestStream())
|
||||
#endif
|
||||
{
|
||||
dataStream.Write(this.payloadData, 0, payloadData.Length);
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using (HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse)
|
||||
#else
|
||||
using(HttpWebResponse response = request.GetResponse() as HttpWebResponse)
|
||||
#endif
|
||||
{
|
||||
using (Stream dataStream = response.GetResponseStream())
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(dataStream))
|
||||
{
|
||||
string responseString = reader.ReadToEnd();
|
||||
body = responseString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
if(e.Response != null)
|
||||
{
|
||||
using (HttpWebResponse response = (HttpWebResponse)e.Response)
|
||||
{
|
||||
using (Stream streamResponse = response.GetResponseStream())
|
||||
{
|
||||
using (StreamReader streamRead = new StreamReader(streamResponse))
|
||||
{
|
||||
string responseString = streamRead.ReadToEnd();
|
||||
|
||||
responseCode = response.StatusCode;
|
||||
responseDescription = response.StatusDescription;
|
||||
|
||||
body = responseString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
GALogger.E(e.ToString());
|
||||
}
|
||||
|
||||
// process the response
|
||||
GALogger.D("sdk error request content : " + body);
|
||||
|
||||
OnPostExecute(responseCode, responseDescription);
|
||||
}
|
||||
|
||||
protected void OnPostExecute(HttpStatusCode responseCode, string responseDescription)
|
||||
{
|
||||
|
||||
if(string.IsNullOrEmpty(this.body))
|
||||
{
|
||||
GALogger.D("sdk error failed. Might be no connection. Description: " + responseDescription + ", Status code: " + responseCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if(responseCode != HttpStatusCode.OK)
|
||||
{
|
||||
GALogger.W("sdk error failed. response code not 200. status code: " + responseCode + ", description: " + responseDescription + ", body: " + this.body);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
countMap[this.type] = countMap[this.type] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
using Windows.System.Threading;
|
||||
using Windows.Foundation;
|
||||
using System.Threading.Tasks;
|
||||
#else
|
||||
using System.Threading;
|
||||
#endif
|
||||
using GameAnalyticsSDK.Net.Logging;
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Threading
|
||||
{
|
||||
public class GAThreading
|
||||
{
|
||||
private static bool endThread = false;
|
||||
private static DateTime threadDeadline;
|
||||
private static readonly GAThreading _instance = new GAThreading ();
|
||||
private const int ThreadWaitTimeInMs = 1000;
|
||||
private readonly PriorityQueue<long, TimedBlock> blocks = new PriorityQueue<long, TimedBlock>();
|
||||
private readonly object threadLock = new object();
|
||||
private TimedBlock scheduledBlock;
|
||||
private bool hasScheduledBlockRun;
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
private IAsyncAction thread;
|
||||
#else
|
||||
private Thread thread;
|
||||
#endif
|
||||
|
||||
private GAThreading()
|
||||
{
|
||||
threadDeadline = DateTime.Now;
|
||||
hasScheduledBlockRun = true;
|
||||
}
|
||||
|
||||
~GAThreading()
|
||||
{
|
||||
StopThread();
|
||||
}
|
||||
|
||||
private static GAThreading Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunBlocks()
|
||||
{
|
||||
TimedBlock timedBlock;
|
||||
|
||||
while ((timedBlock = GetNextBlock()) != null)
|
||||
{
|
||||
timedBlock.block();
|
||||
}
|
||||
|
||||
if ((timedBlock = GetScheduledBlock()) != null)
|
||||
{
|
||||
timedBlock.block();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
GALogger.D("Starting GA thread");
|
||||
|
||||
try
|
||||
{
|
||||
while(!endThread && threadDeadline.CompareTo(DateTime.Now) > 0)
|
||||
{
|
||||
RunBlocks();
|
||||
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
Task.Delay(ThreadWaitTimeInMs).Wait();
|
||||
#else
|
||||
Thread.Sleep(ThreadWaitTimeInMs);
|
||||
#endif
|
||||
}
|
||||
|
||||
// run any last blocks added
|
||||
RunBlocks();
|
||||
|
||||
if (!endThread)
|
||||
{
|
||||
GALogger.D("Ending GA thread");
|
||||
}
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
//GALogger.E("Error on GA thread");
|
||||
//GALogger.E(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void PerformTaskOnGAThread(string blockName, Action taskBlock)
|
||||
{
|
||||
PerformTaskOnGAThread(blockName, taskBlock, 0);
|
||||
}
|
||||
|
||||
public static void PerformTaskOnGAThread(string blockName, Action taskBlock, long delayInSeconds)
|
||||
{
|
||||
if(endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock(Instance.threadLock)
|
||||
{
|
||||
DateTime time = DateTime.Now;
|
||||
time = time.AddSeconds(delayInSeconds);
|
||||
|
||||
TimedBlock timedBlock = new TimedBlock(time, taskBlock, blockName);
|
||||
Instance.AddTimedBlock(timedBlock);
|
||||
threadDeadline = time.AddSeconds(10);
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
if (IsThreadFinished())
|
||||
{
|
||||
StartThread();
|
||||
}
|
||||
#else
|
||||
if (IsThreadFinished())
|
||||
{
|
||||
if(Instance.thread != null)
|
||||
{
|
||||
Instance.thread.Join();
|
||||
}
|
||||
StartThread();
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void ScheduleTimer(double interval, string blockName, Action callback)
|
||||
{
|
||||
if (endThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (Instance.threadLock)
|
||||
{
|
||||
if(Instance.hasScheduledBlockRun)
|
||||
{
|
||||
DateTime time = DateTime.Now;
|
||||
time = time.AddSeconds(interval);
|
||||
Instance.scheduledBlock = new TimedBlock(time, callback, blockName);
|
||||
Instance.hasScheduledBlockRun = false;
|
||||
threadDeadline = time.AddSeconds(2);
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
if (IsThreadFinished())
|
||||
{
|
||||
StartThread();
|
||||
}
|
||||
#else
|
||||
if (IsThreadFinished())
|
||||
{
|
||||
if(Instance.thread != null)
|
||||
{
|
||||
Instance.thread.Join();
|
||||
}
|
||||
StartThread();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTimedBlock(TimedBlock timedBlock)
|
||||
{
|
||||
this.blocks.Enqueue(timedBlock.deadline.Ticks, timedBlock);
|
||||
}
|
||||
|
||||
private static TimedBlock GetNextBlock()
|
||||
{
|
||||
lock(Instance.threadLock)
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
if(Instance.blocks.HasItems && Instance.blocks.Peek().deadline.CompareTo(now) <= 0)
|
||||
{
|
||||
return Instance.blocks.Dequeue();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static TimedBlock GetScheduledBlock()
|
||||
{
|
||||
lock (Instance.threadLock)
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
if (!Instance.hasScheduledBlockRun && Instance.scheduledBlock != null && Instance.scheduledBlock.deadline.CompareTo(now) <= 0)
|
||||
{
|
||||
Instance.hasScheduledBlockRun = true;
|
||||
return Instance.scheduledBlock;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartThread()
|
||||
{
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
Instance.thread = ThreadPool.RunAsync(o => Run());
|
||||
#else
|
||||
Instance.thread = new Thread(new ThreadStart(Run));
|
||||
Instance.thread.Priority = ThreadPriority.Lowest;
|
||||
Instance.thread.Start();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void StopThread()
|
||||
{
|
||||
endThread = true;
|
||||
}
|
||||
|
||||
public static bool IsThreadFinished()
|
||||
{
|
||||
#if WINDOWS_WSA || WINDOWS_UWP
|
||||
return Instance.thread == null || Instance.thread.Status != AsyncStatus.Started;
|
||||
#else
|
||||
return Instance.thread == null || !Instance.thread.IsAlive;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Threading
|
||||
{
|
||||
internal class PriorityQueue<TPriority, TItem>
|
||||
{
|
||||
readonly SortedDictionary<TPriority, Queue<TItem>> _subqueues;
|
||||
|
||||
public PriorityQueue(IComparer<TPriority> priorityComparer)
|
||||
{
|
||||
_subqueues = new SortedDictionary<TPriority, Queue<TItem>>(priorityComparer);
|
||||
}
|
||||
|
||||
public PriorityQueue() : this(Comparer<TPriority>.Default) { }
|
||||
|
||||
public void Enqueue(TPriority priority, TItem item)
|
||||
{
|
||||
if (!_subqueues.ContainsKey(priority))
|
||||
{
|
||||
AddQueueOfPriority(priority);
|
||||
}
|
||||
|
||||
_subqueues[priority].Enqueue(item);
|
||||
}
|
||||
|
||||
private void AddQueueOfPriority(TPriority priority)
|
||||
{
|
||||
_subqueues.Add(priority, new Queue<TItem>());
|
||||
}
|
||||
|
||||
public TItem Peek()
|
||||
{
|
||||
if (HasItems)
|
||||
return _subqueues.First().Value.Peek();
|
||||
else
|
||||
throw new InvalidOperationException("The queue is empty");
|
||||
}
|
||||
|
||||
public bool HasItems
|
||||
{
|
||||
get { return _subqueues.Any(); }
|
||||
}
|
||||
|
||||
public TItem Dequeue()
|
||||
{
|
||||
if (_subqueues.Any())
|
||||
return DequeueFromHighPriorityQueue();
|
||||
else
|
||||
throw new InvalidOperationException("The queue is empty");
|
||||
}
|
||||
|
||||
private TItem DequeueFromHighPriorityQueue()
|
||||
{
|
||||
KeyValuePair<TPriority, Queue<TItem>> first = _subqueues.First();
|
||||
TItem nextItem = first.Value.Dequeue();
|
||||
if (!first.Value.Any())
|
||||
{
|
||||
_subqueues.Remove(first.Key);
|
||||
}
|
||||
return nextItem;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return _subqueues.Sum(q => q.Value.Count); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace GameAnalyticsSDK.Net
|
||||
{
|
||||
internal class TimedBlock : IComparable<TimedBlock>
|
||||
{
|
||||
public readonly DateTime deadline;
|
||||
public readonly Action block;
|
||||
public readonly string blockName;
|
||||
|
||||
public TimedBlock (DateTime deadline, Action block, string blockName)
|
||||
{
|
||||
this.deadline = deadline;
|
||||
this.block = block;
|
||||
this.blockName = blockName;
|
||||
}
|
||||
|
||||
public int CompareTo(TimedBlock other)
|
||||
{
|
||||
return this.deadline.CompareTo (other.deadline);
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return "{TimedBlock: deadLine=" + this.deadline.Ticks + ", block=" + this.blockName + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
#if UNITY
|
||||
using GameAnalyticsSDK.Net.Utilities.Zip.GZip;
|
||||
#else
|
||||
using System.IO.Compression;
|
||||
#endif
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using Windows.Security.Cryptography;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
#else
|
||||
using System.Security.Cryptography;
|
||||
#endif
|
||||
using System.IO;
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Utilities
|
||||
{
|
||||
internal static class GAUtilities
|
||||
{
|
||||
private static readonly DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public static byte[] GzipCompress(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
byte[] result;
|
||||
|
||||
#if !UNITY
|
||||
using (MemoryStream msi = new MemoryStream(Encoding.UTF8.GetBytes(data)))
|
||||
{
|
||||
using (MemoryStream mso = new MemoryStream())
|
||||
{
|
||||
using (GZipStream gs = new GZipStream(mso, CompressionMode.Compress))
|
||||
{
|
||||
msi.CopyTo(gs);
|
||||
}
|
||||
|
||||
result = mso.ToArray();
|
||||
}
|
||||
}
|
||||
#else
|
||||
using (MemoryStream outStream = new MemoryStream())
|
||||
{
|
||||
using (GZipOutputStream tinyStream = new GZipOutputStream(outStream))
|
||||
using (MemoryStream mStream = new MemoryStream(Encoding.UTF8.GetBytes(data)))
|
||||
{
|
||||
mStream.CopyTo(tinyStream);
|
||||
}
|
||||
|
||||
result = outStream.ToArray();
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string HmacWithKey(string key, byte[] data)
|
||||
{
|
||||
byte[] keyByte = Encoding.UTF8.GetBytes(key);
|
||||
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
var hmacsha256 = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
|
||||
#else
|
||||
using (var hmacsha256 = new HMACSHA256(keyByte))
|
||||
#endif
|
||||
{
|
||||
#if WINDOWS_UWP || WINDOWS_WSA
|
||||
var input = data.AsBuffer();
|
||||
var signatureKey = hmacsha256.CreateKey(keyByte.AsBuffer());
|
||||
var cypherMac = CryptographicEngine.Sign(signatureKey, input);
|
||||
return CryptographicBuffer.EncodeToBase64String(cypherMac);
|
||||
#else
|
||||
byte[] hashmessage = hmacsha256.ComputeHash(data);
|
||||
return Convert.ToBase64String(hashmessage);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static bool StringMatch(string s, string pattern)
|
||||
{
|
||||
if(s == null || pattern == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Regex.IsMatch(s, pattern);
|
||||
}
|
||||
|
||||
public static string JoinStringArray(string[] v, string delimiter)
|
||||
{
|
||||
StringBuilder sbStr = new StringBuilder();
|
||||
for (int i = 0, il = v.Length; i < il; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
sbStr.Append(delimiter);
|
||||
}
|
||||
sbStr.Append(v[i]);
|
||||
}
|
||||
return sbStr.ToString();
|
||||
}
|
||||
|
||||
public static bool StringArrayContainsString(string[] array, string search)
|
||||
{
|
||||
if (array.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach(string s in array)
|
||||
{
|
||||
if(s.Equals(search))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static long TimeIntervalSince1970()
|
||||
{
|
||||
TimeSpan interval = DateTime.Now.ToUniversalTime() - origin;
|
||||
return (long)interval.TotalSeconds;
|
||||
}
|
||||
|
||||
public static string ArrayOfObjectsToJsonString(List<JSONNode> arr)
|
||||
{
|
||||
JSONArray json_array = new JSONArray();
|
||||
foreach (JSONNode x in arr)
|
||||
{
|
||||
json_array.Add(x);
|
||||
}
|
||||
return json_array.ToString();
|
||||
}
|
||||
|
||||
public static void CopyTo(this Stream input, Stream output)
|
||||
{
|
||||
byte[] buffer = new byte[16 * 1024]; // Fairly arbitrary size
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
output.Write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1353
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Utilities/SimpleJSON.cs
Normal file
1353
Libraries/GameAnalytics/GA-SDK-MONO-SHARED/Utilities/SimpleJSON.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,297 @@
|
||||
//#define USE_SharpZipLib
|
||||
/* * * * *
|
||||
* This is an extension of the SimpleJSON framework to provide methods to
|
||||
* serialize a JSON object tree into a compact binary format. Optionally the
|
||||
* binary stream can be compressed with the SharpZipLib when using the define
|
||||
* "USE_SharpZipLib"
|
||||
*
|
||||
* Those methods where originally part of the framework but since it's rarely
|
||||
* used I've extracted this part into this seperate module file.
|
||||
*
|
||||
* You can use the define "SimpleJSON_ExcludeBinary" to selectively disable
|
||||
* this extension without the need to remove the file from the project.
|
||||
*
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2012-2017 Markus Göbel (Bunny83)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* * * * */
|
||||
using System;
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Utilities
|
||||
{
|
||||
#if !SimpleJSON_ExcludeBinary
|
||||
public abstract partial class JSONNode
|
||||
{
|
||||
public abstract void SerializeBinary(System.IO.BinaryWriter aWriter);
|
||||
|
||||
public void SaveToBinaryStream(System.IO.Stream aData)
|
||||
{
|
||||
var W = new System.IO.BinaryWriter(aData);
|
||||
SerializeBinary(W);
|
||||
}
|
||||
|
||||
#if USE_SharpZipLib
|
||||
public void SaveToCompressedStream(System.IO.Stream aData)
|
||||
{
|
||||
using (var gzipOut = new ICSharpCode.SharpZipLib.BZip2.BZip2OutputStream(aData))
|
||||
{
|
||||
gzipOut.IsStreamOwner = false;
|
||||
SaveToBinaryStream(gzipOut);
|
||||
gzipOut.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveToCompressedFile(string aFileName)
|
||||
{
|
||||
|
||||
System.IO.Directory.CreateDirectory((new System.IO.FileInfo(aFileName)).Directory.FullName);
|
||||
using(var F = System.IO.File.OpenWrite(aFileName))
|
||||
{
|
||||
SaveToCompressedStream(F);
|
||||
}
|
||||
}
|
||||
public string SaveToCompressedBase64()
|
||||
{
|
||||
using (var stream = new System.IO.MemoryStream())
|
||||
{
|
||||
SaveToCompressedStream(stream);
|
||||
stream.Position = 0;
|
||||
return System.Convert.ToBase64String(stream.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
public void SaveToCompressedStream(System.IO.Stream aData)
|
||||
{
|
||||
throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON");
|
||||
}
|
||||
|
||||
public void SaveToCompressedFile(string aFileName)
|
||||
{
|
||||
throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON");
|
||||
}
|
||||
|
||||
public string SaveToCompressedBase64()
|
||||
{
|
||||
throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON");
|
||||
}
|
||||
#endif
|
||||
|
||||
/*public void SaveToBinaryFile(string aFileName)
|
||||
{
|
||||
System.IO.Directory.CreateDirectory((new System.IO.FileInfo(aFileName)).Directory.FullName);
|
||||
using (var F = System.IO.File.OpenWrite(aFileName))
|
||||
{
|
||||
SaveToBinaryStream(F);
|
||||
}
|
||||
}*/
|
||||
|
||||
public string SaveToBinaryBase64()
|
||||
{
|
||||
using (var stream = new System.IO.MemoryStream())
|
||||
{
|
||||
SaveToBinaryStream(stream);
|
||||
stream.Position = 0;
|
||||
return System.Convert.ToBase64String(stream.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public static JSONNode DeserializeBinary(System.IO.BinaryReader aReader)
|
||||
{
|
||||
JSONNodeType type = (JSONNodeType)aReader.ReadByte();
|
||||
switch (type)
|
||||
{
|
||||
case JSONNodeType.Array:
|
||||
{
|
||||
int count = aReader.ReadInt32();
|
||||
JSONArray tmp = new JSONArray();
|
||||
for (int i = 0; i < count; i++)
|
||||
tmp.Add(DeserializeBinary(aReader));
|
||||
return tmp;
|
||||
}
|
||||
case JSONNodeType.Object:
|
||||
{
|
||||
int count = aReader.ReadInt32();
|
||||
JSONObject tmp = new JSONObject();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string key = aReader.ReadString();
|
||||
var val = DeserializeBinary(aReader);
|
||||
tmp.Add(key, val);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
case JSONNodeType.String:
|
||||
{
|
||||
return new JSONString(aReader.ReadString());
|
||||
}
|
||||
case JSONNodeType.Number:
|
||||
{
|
||||
return new JSONNumber(aReader.ReadDouble());
|
||||
}
|
||||
case JSONNodeType.Boolean:
|
||||
{
|
||||
return new JSONBool(aReader.ReadBoolean());
|
||||
}
|
||||
case JSONNodeType.NullValue:
|
||||
{
|
||||
return JSONNull.CreateOrGet();
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new Exception("Error deserializing JSON. Unknown tag: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_SharpZipLib
|
||||
public static JSONNode LoadFromCompressedStream(System.IO.Stream aData)
|
||||
{
|
||||
var zin = new ICSharpCode.SharpZipLib.BZip2.BZip2InputStream(aData);
|
||||
return LoadFromStream(zin);
|
||||
}
|
||||
public static JSONNode LoadFromCompressedFile(string aFileName)
|
||||
{
|
||||
using(var F = System.IO.File.OpenRead(aFileName))
|
||||
{
|
||||
return LoadFromCompressedStream(F);
|
||||
}
|
||||
}
|
||||
public static JSONNode LoadFromCompressedBase64(string aBase64)
|
||||
{
|
||||
var tmp = System.Convert.FromBase64String(aBase64);
|
||||
var stream = new System.IO.MemoryStream(tmp);
|
||||
stream.Position = 0;
|
||||
return LoadFromCompressedStream(stream);
|
||||
}
|
||||
#else
|
||||
public static JSONNode LoadFromCompressedFile(string aFileName)
|
||||
{
|
||||
throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON");
|
||||
}
|
||||
|
||||
public static JSONNode LoadFromCompressedStream(System.IO.Stream aData)
|
||||
{
|
||||
throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON");
|
||||
}
|
||||
|
||||
public static JSONNode LoadFromCompressedBase64(string aBase64)
|
||||
{
|
||||
throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON");
|
||||
}
|
||||
#endif
|
||||
|
||||
public static JSONNode LoadFromBinaryStream(System.IO.Stream aData)
|
||||
{
|
||||
using (var R = new System.IO.BinaryReader(aData))
|
||||
{
|
||||
return DeserializeBinary(R);
|
||||
}
|
||||
}
|
||||
|
||||
/*public static JSONNode LoadFromBinaryFile(string aFileName)
|
||||
{
|
||||
using (var F = System.IO.File.OpenRead(aFileName))
|
||||
{
|
||||
return LoadFromBinaryStream(F);
|
||||
}
|
||||
}*/
|
||||
|
||||
public static JSONNode LoadFromBinaryBase64(string aBase64)
|
||||
{
|
||||
var tmp = System.Convert.FromBase64String(aBase64);
|
||||
var stream = new System.IO.MemoryStream(tmp);
|
||||
stream.Position = 0;
|
||||
return LoadFromBinaryStream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class JSONArray : JSONNode
|
||||
{
|
||||
public override void SerializeBinary(System.IO.BinaryWriter aWriter)
|
||||
{
|
||||
aWriter.Write((byte)JSONNodeType.Array);
|
||||
aWriter.Write(m_List.Count);
|
||||
for (int i = 0; i < m_List.Count; i++)
|
||||
{
|
||||
m_List[i].SerializeBinary(aWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class JSONObject : JSONNode
|
||||
{
|
||||
public override void SerializeBinary(System.IO.BinaryWriter aWriter)
|
||||
{
|
||||
aWriter.Write((byte)JSONNodeType.Object);
|
||||
aWriter.Write(m_Dict.Count);
|
||||
foreach (string K in m_Dict.Keys)
|
||||
{
|
||||
aWriter.Write(K);
|
||||
m_Dict[K].SerializeBinary(aWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class JSONString : JSONNode
|
||||
{
|
||||
public override void SerializeBinary(System.IO.BinaryWriter aWriter)
|
||||
{
|
||||
aWriter.Write((byte)JSONNodeType.String);
|
||||
aWriter.Write(m_Data);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class JSONNumber : JSONNode
|
||||
{
|
||||
public override void SerializeBinary(System.IO.BinaryWriter aWriter)
|
||||
{
|
||||
aWriter.Write((byte)JSONNodeType.Number);
|
||||
aWriter.Write(m_Data);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class JSONBool : JSONNode
|
||||
{
|
||||
public override void SerializeBinary(System.IO.BinaryWriter aWriter)
|
||||
{
|
||||
aWriter.Write((byte)JSONNodeType.Boolean);
|
||||
aWriter.Write(m_Data);
|
||||
}
|
||||
}
|
||||
public partial class JSONNull : JSONNode
|
||||
{
|
||||
public override void SerializeBinary(System.IO.BinaryWriter aWriter)
|
||||
{
|
||||
aWriter.Write((byte)JSONNodeType.NullValue);
|
||||
}
|
||||
}
|
||||
internal partial class JSONLazyCreator : JSONNode
|
||||
{
|
||||
public override void SerializeBinary(System.IO.BinaryWriter aWriter)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,631 @@
|
||||
using System;
|
||||
using GameAnalyticsSDK.Net.Logging;
|
||||
using GameAnalyticsSDK.Net.Utilities;
|
||||
using GameAnalyticsSDK.Net.State;
|
||||
using GameAnalyticsSDK.Net.Http;
|
||||
|
||||
namespace GameAnalyticsSDK.Net.Validators
|
||||
{
|
||||
internal static class GAValidator
|
||||
{
|
||||
#region Public methods
|
||||
|
||||
public static bool ValidateBusinessEvent(string currency, long amount, string cartType, string itemType, string itemId)
|
||||
{
|
||||
// validate currency
|
||||
if (!ValidateCurrency(currency))
|
||||
{
|
||||
GALogger.W("Validation fail - business event - currency: Cannot be (null) and need to be A-Z, 3 characters and in the standard at openexchangerates.org. Failed currency: " + currency);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amount < 0)
|
||||
{
|
||||
GALogger.W("Validation fail - business event - amount. Cannot be less than 0. Failed amount: " + amount);
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate cartType
|
||||
if (!ValidateShortString(cartType, true))
|
||||
{
|
||||
GALogger.W("Validation fail - business event - cartType. Cannot be above 32 length. String: " + cartType);
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate itemType length
|
||||
if (!ValidateEventPartLength(itemType, false))
|
||||
{
|
||||
GALogger.W("Validation fail - business event - itemType: Cannot be (null), empty or above 64 characters. String: " + itemType);
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate itemType chars
|
||||
if (!ValidateEventPartCharacters(itemType))
|
||||
{
|
||||
GALogger.W("Validation fail - business event - itemType: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " + itemType);
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate itemId
|
||||
if (!ValidateEventPartLength(itemId, false))
|
||||
{
|
||||
GALogger.W("Validation fail - business event - itemId. Cannot be (null), empty or above 64 characters. String: " + itemId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidateEventPartCharacters(itemId))
|
||||
{
|
||||
GALogger.W("Validation fail - business event - itemId: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " + itemId);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateResourceEvent(EGAResourceFlowType flowType, string currency, long amount, string itemType, string itemId)
|
||||
{
|
||||
if (flowType == EGAResourceFlowType.Undefined)
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - flowType: Invalid flow type.");
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrEmpty(currency))
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - currency: Cannot be (null)");
|
||||
return false;
|
||||
}
|
||||
if (!GAState.HasAvailableResourceCurrency(currency))
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - currency: Not found in list of pre-defined available resource currencies. String: " + currency);
|
||||
return false;
|
||||
}
|
||||
if (!(amount > 0))
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - amount: Float amount cannot be 0 or negative. Value: " + amount);
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrEmpty(itemType))
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - itemType: Cannot be (null)");
|
||||
return false;
|
||||
}
|
||||
if (!ValidateEventPartLength(itemType, false))
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - itemType: Cannot be (null), empty or above 64 characters. String: " + itemType);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateEventPartCharacters(itemType))
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - itemType: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " + itemType);
|
||||
return false;
|
||||
}
|
||||
if (!GAState.HasAvailableResourceItemType(itemType))
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - itemType: Not found in list of pre-defined available resource itemTypes. String: " + itemType);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateEventPartLength(itemId, false))
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - itemId: Cannot be (null), empty or above 64 characters. String: " + itemId);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateEventPartCharacters(itemId))
|
||||
{
|
||||
GALogger.W("Validation fail - resource event - itemId: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " + itemId);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateProgressionEvent(EGAProgressionStatus progressionStatus, string progression01, string progression02, string progression03)
|
||||
{
|
||||
if (progressionStatus == EGAProgressionStatus.Undefined)
|
||||
{
|
||||
GALogger.W("Validation fail - progression event: Invalid progression status.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure progressions are defined as either 01, 01+02 or 01+02+03
|
||||
if (!string.IsNullOrEmpty(progression03) && !(!string.IsNullOrEmpty(progression02) || string.IsNullOrEmpty(progression01)))
|
||||
{
|
||||
GALogger.W("Validation fail - progression event: 03 found but 01+02 are invalid. Progression must be set as either 01, 01+02 or 01+02+03.");
|
||||
return false;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(progression02) && string.IsNullOrEmpty(progression01))
|
||||
{
|
||||
GALogger.W("Validation fail - progression event: 02 found but not 01. Progression must be set as either 01, 01+02 or 01+02+03");
|
||||
return false;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(progression01))
|
||||
{
|
||||
GALogger.W("Validation fail - progression event: progression01 not valid. Progressions must be set as either 01, 01+02 or 01+02+03");
|
||||
return false;
|
||||
}
|
||||
|
||||
// progression01 (required)
|
||||
if (!ValidateEventPartLength(progression01, false))
|
||||
{
|
||||
GALogger.W("Validation fail - progression event - progression01: Cannot be (null), empty or above 64 characters. String: " + progression01);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateEventPartCharacters(progression01))
|
||||
{
|
||||
GALogger.W("Validation fail - progression event - progression01: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " + progression01);
|
||||
return false;
|
||||
}
|
||||
// progression02
|
||||
if (!string.IsNullOrEmpty(progression02))
|
||||
{
|
||||
if (!ValidateEventPartLength(progression02, true))
|
||||
{
|
||||
GALogger.W("Validation fail - progression event - progression02: Cannot be empty or above 64 characters. String: " + progression02);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateEventPartCharacters(progression02))
|
||||
{
|
||||
GALogger.W("Validation fail - progression event - progression02: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " + progression02);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// progression03
|
||||
if (!string.IsNullOrEmpty(progression03))
|
||||
{
|
||||
if (!ValidateEventPartLength(progression03, true))
|
||||
{
|
||||
GALogger.W("Validation fail - progression event - progression03: Cannot be empty or above 64 characters. String: " + progression03);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateEventPartCharacters(progression03))
|
||||
{
|
||||
GALogger.W("Validation fail - progression event - progression03: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " + progression03);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateDesignEvent(string eventId, double value)
|
||||
{
|
||||
if (!ValidateEventIdLength(eventId))
|
||||
{
|
||||
GALogger.W("Validation fail - design event - eventId: Cannot be (null) or empty. Only 5 event parts allowed seperated by :. Each part need to be 32 characters or less. String: " + eventId);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateEventIdCharacters(eventId))
|
||||
{
|
||||
GALogger.W("Validation fail - design event - eventId: Non valid characters. Only allowed A-z, 0-9, -_., ()!?. String: " + eventId);
|
||||
return false;
|
||||
}
|
||||
// value: allow 0, negative and nil (not required)
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateErrorEvent(EGAErrorSeverity severity, string message)
|
||||
{
|
||||
if (severity == EGAErrorSeverity.Undefined)
|
||||
{
|
||||
GALogger.W("Validation fail - error event - severity: Severity was unsupported value.");
|
||||
return false;
|
||||
}
|
||||
if (!ValidateLongString(message, true))
|
||||
{
|
||||
GALogger.W("Validation fail - error event - message: Message cannot be above 8192 characters.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateSdkErrorEvent(string gameKey, string gameSecret, EGASdkErrorType type)
|
||||
{
|
||||
if(!ValidateKeys(gameKey, gameSecret))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == EGASdkErrorType.Undefined)
|
||||
{
|
||||
GALogger.W("Validation fail - sdk error event - type: Type was unsupported value.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateKeys(string gameKey, string gameSecret)
|
||||
{
|
||||
if (GAUtilities.StringMatch(gameKey, "^[A-z0-9]{32}$"))
|
||||
{
|
||||
if (GAUtilities.StringMatch(gameSecret, "^[A-z0-9]{40}$"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ValidateCurrency(string currency)
|
||||
{
|
||||
if (string.IsNullOrEmpty(currency))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!GAUtilities.StringMatch(currency, "^[A-Z]{3}$"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateEventPartLength(string eventPart, bool allowNull)
|
||||
{
|
||||
if (allowNull == true && string.IsNullOrEmpty(eventPart))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(eventPart))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventPart.Length > 64)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateEventPartCharacters(string eventPart)
|
||||
{
|
||||
if (!GAUtilities.StringMatch(eventPart, "^[A-Za-z0-9\\s\\-_\\.\\(\\)\\!\\?]{1,64}$"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateEventIdLength(string eventId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(eventId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GAUtilities.StringMatch(eventId, "^[^:]{1,64}(?::[^:]{1,64}){0,4}$"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateEventIdCharacters(string eventId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(eventId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GAUtilities.StringMatch(eventId, "^[A-Za-z0-9\\s\\-_\\.\\(\\)\\!\\?]{1,64}(:[A-Za-z0-9\\s\\-_\\.\\(\\)\\!\\?]{1,64}){0,4}$"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static JSONObject ValidateAndCleanInitRequestResponse(JSONNode initResponse)
|
||||
{
|
||||
// make sure we have a valid dict
|
||||
if (initResponse == null)
|
||||
{
|
||||
GALogger.W("validateInitRequestResponse failed - no response dictionary.");
|
||||
return null;
|
||||
}
|
||||
|
||||
JSONObject validatedDict = new JSONObject();
|
||||
|
||||
// validate enabled field
|
||||
try
|
||||
{
|
||||
validatedDict.Add("enabled", new JSONBool(initResponse["enabled"].IsBoolean ? initResponse["enabled"].AsBool : true));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
GALogger.W("validateInitRequestResponse failed - invalid type in 'enabled' field.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// validate server_ts
|
||||
try
|
||||
{
|
||||
long serverTsNumber = initResponse["server_ts"].IsNumber ? initResponse["server_ts"].AsLong : -1;
|
||||
if (serverTsNumber > 0)
|
||||
{
|
||||
validatedDict.Add("server_ts", new JSONNumber(serverTsNumber));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GALogger.W("validateInitRequestResponse failed - invalid type in 'server_ts' field. type=" + initResponse["server_ts"].GetType() + ", value=" + initResponse["server_ts"] + ", " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// validate configurations field
|
||||
try
|
||||
{
|
||||
validatedDict.Add("configurations", initResponse["configurations"].IsArray ? initResponse["configurations"].AsArray : new JSONArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GALogger.W("validateInitRequestResponse failed - invalid type in 'configurations' field. type=" + initResponse["configurations"].GetType() + ", value=" + initResponse["configurations"] + ", " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return validatedDict;
|
||||
}
|
||||
|
||||
public static bool ValidateBuild(string build)
|
||||
{
|
||||
if (!ValidateShortString(build, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateSdkWrapperVersion(string wrapperVersion)
|
||||
{
|
||||
if (!GAUtilities.StringMatch(wrapperVersion, "^(unity) [0-9]{0,5}(\\.[0-9]{0,5}){0,2}$"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateEngineVersion(string engineVersion)
|
||||
{
|
||||
if (engineVersion == null || !GAUtilities.StringMatch(engineVersion, "^(unity) [0-9]{0,5}(\\.[0-9]{0,5}){0,2}$"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateUserId(string uId)
|
||||
{
|
||||
if (!ValidateString(uId, false))
|
||||
{
|
||||
GALogger.W("Validation fail - user id: id cannot be (null), empty or above 64 characters.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateShortString(string shortString, bool canBeEmpty)
|
||||
{
|
||||
// String is allowed to be empty or nil
|
||||
if (canBeEmpty && string.IsNullOrEmpty(shortString))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(shortString) || shortString.Length > 32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateString(string s, bool canBeEmpty)
|
||||
{
|
||||
// String is allowed to be empty or nil
|
||||
if (canBeEmpty && string.IsNullOrEmpty(s))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(s) || s.Length > 64)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateLongString(string longString, bool canBeEmpty)
|
||||
{
|
||||
// String is allowed to be empty
|
||||
if (canBeEmpty && string.IsNullOrEmpty(longString))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(longString) || longString.Length > 8192)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateConnectionType(string connectionType)
|
||||
{
|
||||
return GAUtilities.StringMatch(connectionType, "^(wwan|wifi|lan|offline)$");
|
||||
}
|
||||
|
||||
public static bool ValidateCustomDimensions(params string[] customDimensions)
|
||||
{
|
||||
return ValidateArrayOfStrings(20, 32, false, "custom dimensions", customDimensions);
|
||||
}
|
||||
|
||||
public static bool ValidateResourceCurrencies(params string[] resourceCurrencies)
|
||||
{
|
||||
if (!ValidateArrayOfStrings(20, 64, false, "resource currencies", resourceCurrencies))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate each string for regex
|
||||
foreach (string resourceCurrency in resourceCurrencies)
|
||||
{
|
||||
if (!GAUtilities.StringMatch(resourceCurrency, "^[A-Za-z]+$"))
|
||||
{
|
||||
GALogger.W("resource currencies validation failed: a resource currency can only be A-Z, a-z. String was: " + resourceCurrency);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateResourceItemTypes(params string[] resourceItemTypes)
|
||||
{
|
||||
if (!ValidateArrayOfStrings(20, 32, false, "resource item types", resourceItemTypes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate each resourceItemType for eventpart validation
|
||||
foreach (string resourceItemType in resourceItemTypes)
|
||||
{
|
||||
if (!ValidateEventPartCharacters(resourceItemType))
|
||||
{
|
||||
GALogger.W("resource item types validation failed: a resource item type cannot contain other characters than A-z, 0-9, -_., ()!?. String was: " + resourceItemType);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateDimension01(string dimension01)
|
||||
{
|
||||
// allow nil
|
||||
if (string.IsNullOrEmpty(dimension01))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!GAState.HasAvailableCustomDimensions01(dimension01))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateDimension02(string dimension02)
|
||||
{
|
||||
// allow nil
|
||||
if (string.IsNullOrEmpty(dimension02))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!GAState.HasAvailableCustomDimensions02(dimension02))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateDimension03(string dimension03)
|
||||
{
|
||||
// allow nil
|
||||
if (string.IsNullOrEmpty(dimension03))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!GAState.HasAvailableCustomDimensions03(dimension03))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateArrayOfStrings(long maxCount, long maxStringLength, bool allowNoValues, string logTag, params string[] arrayOfStrings)
|
||||
{
|
||||
string arrayTag = logTag;
|
||||
|
||||
// use arrayTag to annotate warning log
|
||||
if (string.IsNullOrEmpty(arrayTag))
|
||||
{
|
||||
arrayTag = "Array";
|
||||
}
|
||||
|
||||
if(arrayOfStrings == null)
|
||||
{
|
||||
GALogger.W(arrayTag + " validation failed: array cannot be null. ");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if empty
|
||||
if (allowNoValues == false && arrayOfStrings.Length == 0)
|
||||
{
|
||||
GALogger.W(arrayTag + " validation failed: array cannot be empty. ");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if exceeding max count
|
||||
if (maxCount > 0 && arrayOfStrings.Length > maxCount)
|
||||
{
|
||||
GALogger.W(arrayTag + " validation failed: array cannot exceed " + maxCount + " values. It has " + arrayOfStrings.Length + " values.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate each string
|
||||
foreach (string arrayString in arrayOfStrings)
|
||||
{
|
||||
int stringLength = arrayString == null ? 0 : arrayString.Length;
|
||||
// check if empty (not allowed)
|
||||
if (stringLength == 0)
|
||||
{
|
||||
GALogger.W(arrayTag + " validation failed: contained an empty string.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if exceeding max length
|
||||
if (maxStringLength > 0 && stringLength > maxStringLength)
|
||||
{
|
||||
GALogger.W(arrayTag + " validation failed: a string exceeded max allowed length (which is: " + maxStringLength + "). String was: " + arrayString);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateFacebookId(string facebookId)
|
||||
{
|
||||
if (!ValidateString(facebookId, false))
|
||||
{
|
||||
GALogger.W("Validation fail - facebook id: id cannot be (null), empty or above 64 characters.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateGender(EGAGender gender)
|
||||
{
|
||||
if (gender == EGAGender.Undefined || !(gender == EGAGender.Male || gender == EGAGender.Female))
|
||||
{
|
||||
GALogger.W("Validation fail - gender: Has to be 'male' or 'female'.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateBirthyear(long birthYear)
|
||||
{
|
||||
if (birthYear < 0 || birthYear > 9999)
|
||||
{
|
||||
GALogger.W("Validation fail - birthYear: Cannot be (null) or invalid range.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateClientTs(long clientTs)
|
||||
{
|
||||
// TODO(nikolaj): validate other way? (instead of max possible)
|
||||
if (clientTs < (long.MinValue+1) || clientTs > (long.MaxValue-1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion // Public methods
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<AssemblyName>GameAnalytics.NetStandard</AssemblyName>
|
||||
<RootNamespace>GameAnalytics.Net</RootNamespace>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<Authors>Game Analytics</Authors>
|
||||
<Copyright>Copyright (c) 2016 Game Analytics</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;MONO</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DefineConstants>TRACE;MONO</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DefineConstants>TRACE;MONO</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DefineConstants>TRACE;MONO</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="4.6.7" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.111" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\GA-SDK-MONO-SHARED\GA-SDK-MONO-SHARED.projitems" Label="Shared" />
|
||||
|
||||
</Project>
|
||||
21
Libraries/GameAnalytics/LICENSE
Normal file
21
Libraries/GameAnalytics/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Game Analytics
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Reference in New Issue
Block a user