/*
* Copyright 2012-2013 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 Amazon.Util.Internal;
using Mono.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using ThirdParty.Json.LitJson;
namespace Amazon.MobileAnalytics.MobileAnalyticsManager.Internal
{
public partial class SQLiteEventStore : IEventStore
{
private SqliteConnection connection;
///
/// Implements the Dispose pattern
///
/// Whether this object is being disposed via a call to Dispose
/// or garbage collected.
protected virtual void Dispose(bool disposing)
{
if (!this._isDisposed)
{
if (disposing)
{
if (connection != null)
{
connection.Close();
connection.Dispose();
}
}
this._isDisposed = true;
}
}
private void CreateOrOpenDatabase()
{
lock (_lock)
{
this.DBfileFullPath = System.IO.Path.Combine(AmazonHookedPlatformInfo.Instance.PersistentDataPath, dbFileName);
string vacuumCommand = "PRAGMA auto_vacuum = 1";
string sqlCommand = string.Format(CultureInfo.InvariantCulture, "CREATE TABLE IF NOT EXISTS {0} ({1} TEXT NOT NULL,{2} TEXT NOT NULL UNIQUE,{3} TEXT NOT NULL, {4} INTEGER NOT NULL DEFAULT 0 )",
TABLE_NAME, EVENT_COLUMN_NAME, EVENT_ID_COLUMN_NAME, MA_APP_ID_COLUMN_NAME, EVENT_DELIVERY_ATTEMPT_COUNT_COLUMN_NAME);
if (!File.Exists(this.DBfileFullPath))
{
string directory = Path.GetDirectoryName(this.DBfileFullPath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
SqliteConnection.CreateFile(this.DBfileFullPath);
}
try
{
connection = new SqliteConnection("URI=file:" + this.DBfileFullPath);
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = vacuumCommand;
command.ExecuteNonQuery();
}
using (var command = connection.CreateCommand())
{
command.CommandText = sqlCommand;
command.ExecuteNonQuery();
}
}
finally
{
if (connection != null)
{
connection.Close();
connection.Dispose();
}
}
}
}
///
/// Sets up database.
///
private void SetupSQLiteEventStore()
{
CreateOrOpenDatabase();
}
///
/// Add an event to the store.
///
/// true, if event was put, false otherwise.
public void PutEvent(string eventString, string appId)
{
bool proceedToInsert = false;
long currentDatabaseSize = DatabaseSize;
if (string.IsNullOrEmpty(appId))
throw new ArgumentNullException("AppId");
if (currentDatabaseSize >= _maConfig.MaxDBSize)
{
proceedToInsert = false;
InvalidOperationException e = new InvalidOperationException();
_logger.Error(e, "The database size has exceeded the threshold limit. Unable to insert any new events");
}
else if ((double)currentDatabaseSize / (double)_maConfig.MaxDBSize >= _maConfig.DBWarningThreshold)
{
proceedToInsert = true;
_logger.InfoFormat("The database size is almost full");
}
else
{
proceedToInsert = true;
}
//keep the lock as short as possible
if (proceedToInsert)
{
lock (_lock)
{
string query = string.Format(CultureInfo.InvariantCulture, "INSERT INTO {0} ({1},{2},{3}) values(@eventString , @eventId , @appid )", TABLE_NAME, EVENT_COLUMN_NAME, EVENT_ID_COLUMN_NAME, MA_APP_ID_COLUMN_NAME);
try
{
connection = new SqliteConnection("URI=file:" + this.DBfileFullPath);
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = query;
BindData(command, eventString, Guid.NewGuid().ToString(), appId);
command.ExecuteNonQuery();
}
}
finally
{
if (connection != null)
{
connection.Close();
connection.Dispose();
}
}
}
}
}
///
/// Deletes a list of events.
///
/// true, if events was deleted, false otherwise.
/// Row identifiers.
public void DeleteEvent(List rowIds)
{
lock (_lock)
{
try
{
connection = new SqliteConnection("URI=file:" + this.DBfileFullPath);
connection.Open();
string ids = string.Format(CultureInfo.InvariantCulture, "'{0}'", string.Join("', '", rowIds.ToArray()));
string query = string.Format(CultureInfo.InvariantCulture, "DELETE FROM {0} WHERE {1} IN ({2})", TABLE_NAME, EVENT_ID_COLUMN_NAME, ids);
using (var command = connection.CreateCommand())
{
command.CommandText = query;
command.ExecuteNonQuery();
}
}
finally
{
if (connection != null)
{
connection.Close();
connection.Dispose();
}
}
}
}
///
/// Get All event from the Event Store
///
/// Appid.
/// maximum number of events to fetch
/// All the events as a List of .
public List GetEvents(string appID, int maxAllowed)
{
List eventList = new List();
lock (_lock)
{
try
{
connection = new SqliteConnection("URI=file:" + this.DBfileFullPath);
connection.Open();
string query = string.Format(CultureInfo.InvariantCulture, "SELECT {0},{1},{2} FROM {3} WHERE {4} = @appId ORDER BY {5}, ROWID LIMIT {6} ",
EVENT_ID_COLUMN_NAME, EVENT_COLUMN_NAME, MA_APP_ID_COLUMN_NAME, TABLE_NAME, MA_APP_ID_COLUMN_NAME, EVENT_DELIVERY_ATTEMPT_COUNT_COLUMN_NAME, maxAllowed);
using (var command = connection.CreateCommand())
{
command.CommandText = query;
BindData(command, appID);
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
JsonData data = new JsonData();
data["id"] = reader.GetString(0);
data["event"] = reader.GetString(1);
data["appId"] = reader.GetString(2);
eventList.Add(data);
}
}
}
}
}
finally
{
if (connection != null)
{
connection.Close();
connection.Dispose();
}
}
}
return eventList;
}
///
/// Gets Numbers the of events.
///
/// The number of events.
public long NumberOfEvents(string appID)
{
long count = 0;
lock (_lock)
{
try
{
connection = new SqliteConnection("URI=file:" + this.DBfileFullPath);
connection.Open();
string query = string.Format(CultureInfo.InvariantCulture, "SELECT COUNT(*) C FROM {0} where {1} = @appId ", TABLE_NAME, MA_APP_ID_COLUMN_NAME);
using (var command = connection.CreateCommand())
{
command.CommandText = query;
BindData(command, appID);
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
UnityEngine.Debug.Log("HAS ROWS = TRUE");
while (reader.Read())
{
count = reader.GetInt32(0);
UnityEngine
.Debug.Log(string.Format("count = {0}", count));
}
}
}
}
}
finally
{
if (connection != null)
{
connection.Close();
connection.Dispose();
}
}
}
return count;
}
///
/// Increments the delivery attempt.
///
/// Success of operation
/// Row identifiers.
public bool IncrementDeliveryAttempt(List rowIds)
{
bool success = false;
lock (_lock)
{
try
{
connection = new SqliteConnection("URI=file:" + this.DBfileFullPath);
connection.Open();
string ids = "'" + String.Join("', '", rowIds.ToArray()) + "'";
string query = String.Format("UPDATE " + TABLE_NAME + " SET " + EVENT_DELIVERY_ATTEMPT_COUNT_COLUMN_NAME + "= " + EVENT_DELIVERY_ATTEMPT_COUNT_COLUMN_NAME + "+1 WHERE " + EVENT_ID_COLUMN_NAME + " IN ({0})", ids);
using (var command = connection.CreateCommand())
{
command.CommandText = query;
command.ExecuteNonQuery();
}
}
finally
{
if (connection != null)
{
connection.Close();
connection.Dispose();
}
}
return success;
}
}
///
/// Gets the size of the database.
///
/// The database size.
public long DatabaseSize
{
get
{
string pageCountCommand = "PRAGMA page_count;";
string pageSizeCommand = "PRAGMA page_size;";
long pageCount = 0, pageSize = 0;
lock (_lock)
{
try
{
connection = new SqliteConnection("URI=file:" + this.DBfileFullPath);
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = pageCountCommand;
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
pageCount = reader.GetInt64(0);
}
}
}
command.CommandText = pageSizeCommand;
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
pageSize = reader.GetInt64(0);
}
}
}
}
}
finally
{
if (connection != null)
{
connection.Close();
connection.Dispose();
}
}
}
return pageCount * pageSize;
}
}
private static void BindData(SqliteCommand command, params object[] parameters)
{
string query = command.CommandText;
int count = 0;
foreach (Match match in Regex.Matches(query, "(\\@\\w+) "))
{
var date = parameters[count] as DateTime?;
if (date.HasValue)
{
command.Parameters.Add(new SqliteParameter(match.Groups[1].Value, date.Value.Ticks.ToString(CultureInfo.InvariantCulture.NumberFormat)));
}
else
{
command.Parameters.Add(new SqliteParameter(match.Groups[1].Value, parameters[count]));
}
count++;
}
}
}
}