using Tango.Synchronization; using System; using System.Collections.Generic; using System.Data; using System.Data.SQLite; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; namespace Tango.Synchronization.Local { /// /// Represents an SQLite database adapter used for synchronization by . /// /// public class SQLiteDataBase : ILocalDataBase { private SQLiteConnection _connection; /// /// Gets the database source URL/File. /// public String Source { get; private set; } /// /// Gets the database tables collection. /// public List Tables { get; private set; } /// /// Initializes a new instance of the class. /// /// The file path. public SQLiteDataBase(String filePath) { Source = filePath; _connection = new SQLiteConnection(String.Format("Data Source={0};Version=3;New={1};Compress=FALSE;", filePath, (File.Exists(filePath) ? "False" : "True") )); _connection.Open(); } /// /// Loads the tables (Must be done before any synchronization). /// public void LoadTables() { Tables = new List(); var command = _connection.CreateCommand(); command.CommandText = "SELECT name FROM sqlite_master WHERE type = 'table'"; var reader = command.ExecuteReader(); while (reader.Read()) { String name = reader.GetString(0); if (name != Constants.SEQUENCE_TABLE_NAME) { Debug.WriteLine(name); Tables.Add(LoadTable(name)); } } } /// /// Loads the table. /// /// The name. /// private DataTable LoadTable(String name) { var tableCommand = _connection.CreateCommand(); tableCommand.CommandText = "SELECT * FROM " + name; DataTable table = new DataTable(name); table.Load(tableCommand.ExecuteReader()); var infoCommand = _connection.CreateCommand(); infoCommand.CommandText = String.Format("PRAGMA table_info({0});", name); DataTable infoTable = new DataTable(name); infoTable.Load(infoCommand.ExecuteReader()); table.ExtendedProperties.Add(Constants.TABLE_INFO, infoTable); for (int i = 0; i < table.Columns.Count; i++) { DataColumn column = table.Columns[i]; column.ExtendedProperties.Add(Constants.COLUMN_TYPE, infoTable.Rows[i][Constants.COLUMN_TYPE]); column.ExtendedProperties.Add(Constants.IS_NOT_NULL, infoTable.Rows[i][Constants.IS_NOT_NULL]); column.ExtendedProperties.Add(Constants.DEFAULT_VALUE, infoTable.Rows[i][Constants.DEFAULT_VALUE]); } return table; } /// /// Clears the data base. /// public void ClearDataBase() { foreach (var table in Tables) { var dropCommand = _connection.CreateCommand(); dropCommand.CommandText = String.Format("DELETE FROM {0};", table.TableName); dropCommand.ExecuteNonQuery(); } } /// /// Clones a table from another database into this database. /// /// The other database. /// The other table. /// public DataTable CloneTableFrom(ILocalDataBase otherDB, DataTable otherTable) { String cmd = String.Format("CREATE TABLE {0} (", otherTable.TableName); foreach (DataColumn column in otherTable.Columns) { cmd += String.Format("{0} {1} {2} {3} {4} {5}", column.ColumnName, column.GetSQLType(), otherTable.PrimaryKey.Contains(column) ? "PRIMARY KEY" : null, column.Unique ? "UNIQUE" : null, column.IsNotNull() ? "NOT NULL" : null, column.HasDefaultValue() ? ("DEFAULT " + column.GetDefaultValue()) : null) + ", " + Environment.NewLine; } cmd += ");"; cmd = cmd.Remove(cmd.LastIndexOf(","), 1); var createCommand = _connection.CreateCommand(); createCommand.CommandText = cmd; createCommand.ExecuteNonQuery(); var attacheCommand = _connection.CreateCommand(); attacheCommand.CommandText = String.Format("ATTACH DATABASE '{0}' AS other;", otherDB.Source); attacheCommand.ExecuteNonQuery(); var copyCommand = _connection.CreateCommand(); copyCommand.CommandText = String.Format("INSERT INTO main.{0} SELECT * FROM {1}.{0};", otherTable.TableName, "other"); copyCommand.ExecuteNonQuery(); var detacheCommand = _connection.CreateCommand(); detacheCommand.CommandText = "DETACH other;"; detacheCommand.ExecuteNonQuery(); var table = LoadTable(otherTable.TableName); Tables.Add(table); return table; } /// /// Replaces the table data with the data from another table. /// /// The other table. public void ReplaceTableData(ILocalDataBase otherDB, DataTable otherTable) { var dropCommand = _connection.CreateCommand(); dropCommand.CommandText = String.Format("DELETE FROM {0};", otherTable.TableName); dropCommand.ExecuteNonQuery(); var attacheCommand = _connection.CreateCommand(); attacheCommand.CommandText = String.Format("ATTACH DATABASE '{0}' AS other;", otherDB.Source); attacheCommand.ExecuteNonQuery(); var copyCommand = _connection.CreateCommand(); copyCommand.CommandText = String.Format("INSERT INTO main.{0} SELECT * FROM {1}.{0};", otherTable.TableName, "other"); copyCommand.ExecuteNonQuery(); var detacheCommand = _connection.CreateCommand(); detacheCommand.CommandText = "DETACH other;"; detacheCommand.ExecuteNonQuery(); } /// /// Gets the SQL command for . /// /// /// The other table. /// public string GetReplaceTableDataCommand(ILocalDataBase otherDB, DataTable otherTable) { String cmd = String.Format("DELETE FROM {0};", otherTable.TableName) + Environment.NewLine; cmd += String.Format("ATTACH DATABASE '{0}' AS other;", otherDB.Source) + Environment.NewLine; cmd += String.Format("INSERT INTO main.{0} SELECT * FROM {1}.{0};", otherTable.TableName, "other") + Environment.NewLine; cmd += "DETACH other;" + Environment.NewLine; return cmd; } /// /// Gets the SQL command for . /// /// The other database. /// The other table. /// public String GetCloneTableFromCommand(ILocalDataBase otherDB, DataTable otherTable) { String cmd = String.Format("CREATE TABLE {0} (", otherTable.TableName); foreach (DataColumn column in otherTable.Columns) { cmd += String.Format("{0} {1} {2} {3} {4} {5}", column.ColumnName, column.GetSQLType(), otherTable.PrimaryKey.Contains(column) ? "PRIMARY KEY" : null, column.Unique ? "UNIQUE" : null, column.IsNotNull() ? "NOT NULL" : null, column.HasDefaultValue() ? ("DEFAULT " + column.GetDefaultValue()) : null) + ", " + Environment.NewLine; } cmd += ");"; cmd = cmd.Remove(cmd.LastIndexOf(","), 1); cmd += Environment.NewLine; cmd += String.Format("ATTACH DATABASE '{0}' AS other;", otherDB.Source); cmd += Environment.NewLine; cmd += String.Format("INSERT INTO main.{0} SELECT * FROM {1}.{0};", otherTable.TableName, "other"); cmd += Environment.NewLine; cmd += "DETACH other;"; return cmd; } /// /// Adds the specified column to the specified table. /// /// The table. /// The column. /// public DataColumn AddColumn(DataTable table, DataColumn column) { var insertCommand = _connection.CreateCommand(); insertCommand.CommandText = String.Format("ALTER TABLE {0} ADD COLUMN {1} {2} {3} {4} {5}", table.TableName, column.ColumnName, column.GetSQLType(), table.PrimaryKey.Contains(column) ? "PRIMARY KEY" : null, column.Unique ? "UNIQUE" : null, //column.IsNotNull() ? "NOT NULL" : null, column.HasDefaultValue() ? ("DEFAULT " + column.GetDefaultValue()) : null); insertCommand.ExecuteNonQuery(); var t = LoadTable(table.TableName); var index = Tables.IndexOf(Tables.Single(x => x.TableName == table.TableName)); Tables[index] = t; return t.Columns[column.ColumnName]; } /// /// Gets the SQL command for . /// /// The table. /// The column. /// public String GetAddColumnCommand(DataTable table, DataColumn column) { String cmd = String.Format("ALTER TABLE {0} ADD COLUMN {1} {2} {3} {4} {5}", table.TableName, column.ColumnName, column.GetSQLType(), table.PrimaryKey.Contains(column) ? "PRIMARY KEY" : null, column.Unique ? "UNIQUE" : null, //column.IsNotNull() ? "NOT NULL" : null, column.HasDefaultValue() ? ("DEFAULT " + column.GetDefaultValue()) : null); return cmd; } /// /// Adds the specified row to the specified table. /// /// The table. /// The row. public void AddRow(DataTable table, DataRow row) { var insertCommand = _connection.CreateCommand(); List values = new List(); for (int i = 0; i < row.ItemArray.Length; i++) { String value = row.ItemArray[i].ToString(); double num = 0; if (row.ItemArray[i].GetType() == typeof(bool)) { value = ((bool)row.ItemArray[i]) ? 1.ToString() : 0.ToString(); } if (row.ItemArray[i].GetType() == typeof(DateTime)) { value = ((DateTime)row.ItemArray[i]).ToSQLiteDateString(); } if (!double.TryParse(value, out num)) { value = "'" + value + "'"; } values.Add(value); } insertCommand.CommandText = String.Format("INSERT INTO {0} VALUES({1});", table.TableName, String.Join(",", values)); insertCommand.ExecuteNonQuery(); var desRow = table.NewRow(); desRow.ItemArray = row.ItemArray.Clone() as object[]; } /// /// Gets the SQL command for . /// /// The table. /// The row. /// public String GetAddRowCommand(DataTable table, DataRow row) { List values = new List(); for (int i = 0; i < row.ItemArray.Length; i++) { String value = row.ItemArray[i].ToString(); double num = 0; if (row.ItemArray[i].GetType() == typeof(bool)) { value = ((bool)row.ItemArray[i]) ? 1.ToString() : 0.ToString(); } if (row.ItemArray[i].GetType() == typeof(DateTime)) { value = ((DateTime)row.ItemArray[i]).ToSQLiteDateString(); } if (!double.TryParse(value, out num)) { value = "'" + value + "'"; } values.Add(value); } String cmd = String.Format("INSERT INTO {0} VALUES({1});", table.TableName, String.Join("," + Environment.NewLine, values)); return cmd; } /// /// Updates the matching row by the row GUID. /// /// The table. /// The row. public void UpdateRow(DataTable table, DataRow row) { var updateCommand = _connection.CreateCommand(); List values = new List(); for (int i = 0; i < row.ItemArray.Length; i++) { String value = row.ItemArray[i].ToString(); double num = 0; if (row.ItemArray[i].GetType() == typeof(bool)) { value = ((bool)row.ItemArray[i]) ? 1.ToString() : 0.ToString(); } if (row.ItemArray[i].GetType() == typeof(DateTime)) { value = ((DateTime)row.ItemArray[i]).ToSQLiteDateString(); } if (!double.TryParse(value, out num)) { value = "'" + value + "'"; } values.Add(value); } String cmd = String.Format("UPDATE {0} SET ", table.TableName); String guid = String.Empty; for (int columnIndex = 0; columnIndex < table.Columns.Count; columnIndex++) { DataColumn column = table.Columns[columnIndex]; if (column.ColumnName == Constants.ID) { continue; } else if (column.ColumnName == Constants.GUID) { guid = values[columnIndex]; continue; } cmd += String.Format("{0} = {1},{2}", column.ColumnName, values[columnIndex], Environment.NewLine); } cmd = cmd.Remove(cmd.LastIndexOf(","), 1); cmd += "WHERE" + Environment.NewLine; cmd += String.Format("{0} = {1};", Constants.GUID, guid); updateCommand.CommandText = cmd; updateCommand.ExecuteNonQuery(); var desRow = table.NewRow(); desRow.ItemArray = row.ItemArray.Clone() as object[]; } /// /// Gets the SQL command for . /// /// The table. /// The row. /// public String GetUpdateRowCommand(DataTable table, DataRow row) { List values = new List(); for (int i = 0; i < row.ItemArray.Length; i++) { String value = row.ItemArray[i].ToString(); double num = 0; if (row.ItemArray[i].GetType() == typeof(bool)) { value = ((bool)row.ItemArray[i]) ? 1.ToString() : 0.ToString(); } if (row.ItemArray[i].GetType() == typeof(DateTime)) { value = ((DateTime)row.ItemArray[i]).ToSQLiteDateString(); } if (!double.TryParse(value, out num)) { value = "'" + value + "'"; } values.Add(value); } String cmd = String.Format("UPDATE {0} SET ", table.TableName); String guid = String.Empty; for (int columnIndex = 0; columnIndex < table.Columns.Count; columnIndex++) { DataColumn column = table.Columns[columnIndex]; if (column.ColumnName == Constants.ID) { continue; } else if (column.ColumnName == Constants.GUID) { guid = values[columnIndex]; continue; } cmd += String.Format("{0} = {1},{2}", column.ColumnName, values[columnIndex], Environment.NewLine); } cmd = cmd.Remove(cmd.LastIndexOf(","), 1); cmd += "WHERE" + Environment.NewLine; cmd += String.Format("{0} = {1};", Constants.GUID, guid); return cmd; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { _connection.Close(); _connection.Dispose(); GC.Collect(); DateTime startTime = DateTime.Now; while (startTime.AddSeconds(2) > DateTime.Now) { try { using (FileStream stream = File.Open(Source, FileMode.Open, FileAccess.Read)) { return; } } catch (IOException) { GC.Collect(); Thread.Sleep(200); } } } } }