/*
* Copyright 2015-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using System.Threading;
using System.Collections;
using System;
using System.IO;
using ThirdParty.Json.LitJson;
using Amazon.Runtime.Internal.Util;
using Amazon.Util;
using Amazon.Util.Internal;
using Amazon.Runtime.Internal;
#if PCL || BCL45
using System.Threading.Tasks;
#endif
#if PCL
using PCLStorage;
#endif
namespace Amazon.MobileAnalytics.MobileAnalyticsManager.Internal
{
///
/// The class controls session start, pause, resume and stop logic.
///
[System.Security.SecuritySafeCritical]
public partial class Session : IDisposable
{
private Logger _logger = Logger.GetLogger(typeof(Session));
// session info
///
/// Session start Time.
///
public DateTime StartTime { get; set; }
///
/// Session stop time.
///
public DateTime? StopTime { get; set; }
///
/// Session latest resume time.
///
public DateTime PreStartTime { get; set; }
///
/// Session ID.
///
public string SessionId { get; set; }
///
/// Session duration in milliseconds.
///
public long Duration { get; set; }
// lock to guard session info
private Object _lock = new Object();
internal class SessionStorage
{
public SessionStorage()
{
_sessionId = null; _duration = 0;
}
public DateTime _startTime;
public DateTime? _stopTime;
public DateTime _preStartTime;
public string _sessionId;
public long _duration;
}
private MobileAnalyticsManagerConfig _maConfig;
private volatile SessionStorage _sessionStorage = null;
private string _appID = null;
private string _sessionStorageFileName = "_session_storage.json";
private string _sessionStorageFileFullPath = null;
#region public
///
/// Constructor of
///
/// Amazon Mobile Analytics App Identifier.
/// Mobile Analytics Manager Configuration.
[System.Security.SecuritySafeCritical]
public Session(string appID, MobileAnalyticsManagerConfig maConfig)
{
_maConfig = maConfig;
_appID = appID;
#if BCL
_sessionStorageFileFullPath = InternalSDKUtils.DetermineAppLocalStoragePath(appID + _sessionStorageFileName);
#elif PCL
_sessionStorageFileFullPath = System.IO.Path.Combine(PCLStorage.FileSystem.Current.LocalStorage.Path, appID + _sessionStorageFileName);
#endif
_logger.InfoFormat("Initialize a new session. The session storage file is {0}.", _sessionStorageFileFullPath);
_sessionStorage = new SessionStorage();
}
///
/// Start this session.
///
public void Start()
{
lock (_lock)
{
// Read session info from persistent storage, in case app is killed.
RetrieveSessionStorage();
// If session storage is valid, restore session and resume session.
if (_sessionStorage != null && !string.IsNullOrEmpty(_sessionStorage._sessionId))
{
this.StartTime = _sessionStorage._startTime;
this.StopTime = _sessionStorage._stopTime;
this.SessionId = _sessionStorage._sessionId;
this.Duration = _sessionStorage._duration;
Resume();
}
// Otherwise, create a new session.
else
{
NewSessionHelper();
}
}
}
///
/// Pause this session.
///
public void Pause()
{
lock (_lock)
{
PauseSessionHelper();
SaveSessionStorage();
}
}
///
/// Resume this session.
///
public void Resume()
{
lock (_lock)
{
if (this.StopTime == null)
{
//this may sometimes be a valid scenario e.g when the applciation starts
_logger.InfoFormat("Call Resume() without calling Pause() first. But this can be valid opertion only when MobileAnalyticsManager instance is created.");
return;
}
DateTime currentTime = AWSSDKUtils.CorrectedUtcNow;
if (this.StopTime.Value < currentTime)
{
// new session
if (Convert.ToInt64((currentTime - this.StopTime.Value).TotalMilliseconds) > _maConfig.SessionTimeout * 1000)
{
StopSessionHelper();
NewSessionHelper();
}
// resume old session
else
{
ResumeSessionHelper();
}
}
else
{
InvalidOperationException e = new InvalidOperationException();
_logger.Error(e, "Session stop time is earlier than start time !");
}
}
}
#endregion
private void NewSessionHelper()
{
StartTime = AWSSDKUtils.CorrectedUtcNow;
PreStartTime = AWSSDKUtils.CorrectedUtcNow;
StopTime = null;
SessionId = Guid.NewGuid().ToString();
Duration = 0;
CustomEvent sessionStartEvent = new CustomEvent(Constants.SESSION_START_EVENT_TYPE);
sessionStartEvent.StartTimestamp = StartTime;
sessionStartEvent.SessionId = SessionId;
MobileAnalyticsManager.GetInstance(_appID).RecordEvent(sessionStartEvent);
}
private void StopSessionHelper()
{
DateTime currentTime = AWSSDKUtils.CorrectedUtcNow;
// update session info
StopTime = currentTime;
// record session stop event
CustomEvent stopSessionEvent = new CustomEvent(Constants.SESSION_STOP_EVENT_TYPE);
stopSessionEvent.StartTimestamp = StartTime;
if (StopTime != null)
stopSessionEvent.StopTimestamp = StopTime;
stopSessionEvent.SessionId = SessionId;
stopSessionEvent.Duration = Duration;
MobileAnalyticsManager.GetInstance(_appID).RecordEvent(stopSessionEvent);
}
private void PauseSessionHelper()
{
DateTime currentTime = AWSSDKUtils.CorrectedUtcNow;
// update session info
StopTime = currentTime;
Duration += Convert.ToInt64((currentTime - PreStartTime).TotalMilliseconds);
// record session pause event
CustomEvent pauseSessionEvent = new CustomEvent(Constants.SESSION_PAUSE_EVENT_TYPE);
pauseSessionEvent.StartTimestamp = StartTime;
if (StopTime != null)
pauseSessionEvent.StopTimestamp = StopTime;
pauseSessionEvent.SessionId = SessionId;
pauseSessionEvent.Duration = Duration;
MobileAnalyticsManager.GetInstance(_appID).RecordEvent(pauseSessionEvent);
}
private void ResumeSessionHelper()
{
DateTime currentTime = AWSSDKUtils.CorrectedUtcNow;
// update session info
PreStartTime = currentTime;
// record session resume event
CustomEvent resumeSessionEvent = new CustomEvent(Constants.SESSION_RESUME_EVENT_TYPE);
resumeSessionEvent.StartTimestamp = StartTime;
if (StopTime != null)
resumeSessionEvent.StopTimestamp = StopTime;
resumeSessionEvent.SessionId = SessionId;
resumeSessionEvent.Duration = Duration;
MobileAnalyticsManager.GetInstance(_appID).RecordEvent(resumeSessionEvent);
}
private void SaveSessionStorage()
{
_sessionStorage._startTime = StartTime;
_sessionStorage._stopTime = StopTime;
_sessionStorage._preStartTime = PreStartTime;
_sessionStorage._sessionId = SessionId;
_sessionStorage._duration = Duration;
// store session into file
_logger.DebugFormat("Mobile Analytics is about to store session info: {0} ", JsonMapper.ToJson(_sessionStorage));
#if PCL
IFolder rootFolder = FileSystem.Current.LocalStorage;
IFile file = rootFolder.CreateFileAsync(_sessionStorageFileFullPath, CreationCollisionOption.ReplaceExisting).Result;
file.WriteAllTextAsync(JsonMapper.ToJson(_sessionStorage)).Wait();
#elif BCL
string directory = Path.GetDirectoryName(_sessionStorageFileFullPath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
File.WriteAllText(_sessionStorageFileFullPath, JsonMapper.ToJson(_sessionStorage));
#endif
}
private void RetrieveSessionStorage()
{
string sessionString = null;
#if PCL
IFolder rootFolder = FileSystem.Current.LocalStorage;
if (ExistenceCheckResult.FileExists == rootFolder.CheckExistsAsync(_sessionStorageFileFullPath).Result)
{
IFile file = rootFolder.GetFileAsync(_sessionStorageFileFullPath).Result;
sessionString = file.ReadAllTextAsync().Result;
}
#elif BCL
if (File.Exists(_sessionStorageFileFullPath))
{
using (var sessionFile = new System.IO.StreamReader(_sessionStorageFileFullPath))
{
sessionString = sessionFile.ReadToEnd();
sessionFile.Close();
}
_logger.DebugFormat("Mobile Analytics retrieves session info: {0}", sessionString);
}
else
{
_logger.DebugFormat("Mobile Analytics session file does not exist.");
}
#endif
if (!string.IsNullOrEmpty(sessionString))
{
_sessionStorage = JsonMapper.ToObject(sessionString);
}
}
#region dispose pattern implementation
///
/// Dispose pattern implementation
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Dispose pattern implementation
///
/// if disposing
protected virtual void Dispose(bool disposing)
{
}
#endregion
}
}