aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.Synchronization
diff options
context:
space:
mode:
authorRoy <roy.mail.net@gmail.com>2017-11-26 00:31:52 +0200
committerRoy <roy.mail.net@gmail.com>2017-11-26 00:31:52 +0200
commit4eff62d399ff3f3093f67185ac58b5d0a134e25e (patch)
tree8337f34b4b8d7f92f4eebb00fd7a65edfa5b65a5 /Software/Visual_Studio/Tango.Synchronization
parentfd60c446a29842227028ad20385e9a99950ea231 (diff)
downloadTango-4eff62d399ff3f3093f67185ac58b5d0a134e25e.tar.gz
Tango-4eff62d399ff3f3093f67185ac58b5d0a134e25e.zip
Implemented SQLite Synchronization Library!
Diffstat (limited to 'Software/Visual_Studio/Tango.Synchronization')
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/App.config24
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/Constants.cs35
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/ExtensionMethods.cs88
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/ISqlDataBase.cs88
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/Properties/AssemblyInfo.cs6
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/SqlDataBaseComparer.cs145
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/SqlDiff.cs65
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/SqlDiffAction.cs40
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/SqliteDataBase.cs436
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/Tango.Synchronization.csproj85
-rw-r--r--Software/Visual_Studio/Tango.Synchronization/packages.config8
11 files changed, 1020 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Tango.Synchronization/App.config b/Software/Visual_Studio/Tango.Synchronization/App.config
new file mode 100644
index 000000000..a5105cd61
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/App.config
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <configSections>
+ <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
+ <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
+ </configSections>
+ <entityFramework>
+ <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
+ <parameters>
+ <parameter value="v13.0" />
+ </parameters>
+ </defaultConnectionFactory>
+ <providers>
+ <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
+ <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
+ </providers>
+ </entityFramework>
+ <system.data>
+ <DbProviderFactories>
+ <remove invariant="System.Data.SQLite.EF6" />
+ <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />
+ <remove invariant="System.Data.SQLite" /><add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" /></DbProviderFactories>
+ </system.data>
+</configuration> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.Synchronization/Constants.cs b/Software/Visual_Studio/Tango.Synchronization/Constants.cs
new file mode 100644
index 000000000..3558fd506
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/Constants.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Synchronization
+{
+ internal static class Constants
+ {
+ //SQLite Internal Constants
+ public const String SEQUENCE_TABLE_NAME = "sqlite_sequence";
+ public const String TABLE_INFO = "table_info";
+ public const String COLUMN_TYPE = "type";
+ public const String IS_NOT_NULL = "notnull";
+ public const String DEFAULT_VALUE = "dflt_value";
+
+ //Must Column Names.
+
+ /// <summary>
+ /// The table auto incremented id column name.
+ /// </summary>
+ public const String ID = "ID";
+
+ /// <summary>
+ /// The table unique identifier column name used for synchronization.
+ /// </summary>
+ public const String GUID = "GUID";
+
+ /// <summary>
+ /// The table last updated column name used for synchronization.
+ /// </summary>
+ public const String LAST_UPDATED = "LAST_UPDATED";
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Synchronization/ExtensionMethods.cs b/Software/Visual_Studio/Tango.Synchronization/ExtensionMethods.cs
new file mode 100644
index 000000000..ba11e0719
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/ExtensionMethods.cs
@@ -0,0 +1,88 @@
+using Tango.Synchronization;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Synchronization
+{
+ internal static class SQLExtensions
+ {
+ /// <summary>
+ /// Gets the type of the SQL column.
+ /// </summary>
+ /// <param name="column">The column.</param>
+ /// <returns></returns>
+ public static String GetSQLType(this DataColumn column)
+ {
+ return column.ExtendedProperties[Constants.COLUMN_TYPE].ToString();
+ }
+
+ /// <summary>
+ /// Gets the information table.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <returns></returns>
+ public static DataTable GetInfoTable(this DataTable table)
+ {
+ return table.ExtendedProperties[Constants.TABLE_INFO] as DataTable;
+ }
+
+ /// <summary>
+ /// Determines whether column is marked NOT NULL.
+ /// </summary>
+ /// <param name="column">The column.</param>
+ public static bool IsNotNull(this DataColumn column)
+ {
+ int value = int.Parse(column.ExtendedProperties[Constants.IS_NOT_NULL].ToString());
+ return value == 0 ? false : true;
+ }
+
+ /// <summary>
+ /// Gets the column default value.
+ /// </summary>
+ /// <param name="column">The column.</param>
+ /// <returns></returns>
+ public static String GetDefaultValue(this DataColumn column)
+ {
+ String def = column.ExtendedProperties[Constants.DEFAULT_VALUE].ToString();
+
+ double num = -1;
+
+ if (double.TryParse(def, out num))
+ {
+ return num.ToString();
+ }
+ else
+ {
+ if (def.Contains("HEX"))
+ {
+ def = def.Insert(0, "(");
+ def = def.Insert(def.Length - 1, ")");
+ }
+ return def;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether column definition has default value.
+ /// </summary>
+ /// <param name="column">The column.</param>
+ public static bool HasDefaultValue(this DataColumn column)
+ {
+ return !String.IsNullOrWhiteSpace(column.ExtendedProperties[Constants.DEFAULT_VALUE].ToString());
+ }
+
+ /// <summary>
+ /// Converts the DateTime instance to SQLite DateTime string.
+ /// </summary>
+ /// <param name="date">The date.</param>
+ /// <returns></returns>
+ public static String ToSQLiteDateString(this DateTime date)
+ {
+ return date.ToString("yyyy-MM-dd HH:mm:ss.fff");
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.Synchronization/ISqlDataBase.cs b/Software/Visual_Studio/Tango.Synchronization/ISqlDataBase.cs
new file mode 100644
index 000000000..f087dfd15
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/ISqlDataBase.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Synchronization
+{
+ /// <summary>
+ /// Represents an SQL data base adapter used by <see cref="SqlDataBaseComparer"/>.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
+ public interface ISqlDataBase : IDisposable
+ {
+ /// <summary>
+ /// Gets the database source URL/File.
+ /// </summary>
+ String Source { get; }
+
+ /// <summary>
+ /// Gets the database tables collection.
+ /// </summary>
+ List<DataTable> Tables { get; }
+
+ /// <summary>
+ /// Clones a table from another database into this database.
+ /// </summary>
+ /// <param name="otherDB">The other database.</param>
+ /// <param name="otherTable">The other table.</param>
+ /// <returns></returns>
+ DataTable CloneTableFrom(ISqlDataBase otherDB, DataTable otherTable);
+
+ /// <summary>
+ /// Gets the SQL command for <see cref="CloneTableFrom(ISqlDataBase, DataTable)"/>.
+ /// </summary>
+ /// <param name="otherDB">The other database.</param>
+ /// <param name="otherTable">The other table.</param>
+ /// <returns></returns>
+ String GetCloneTableFromCommand(ISqlDataBase otherDB, DataTable otherTable);
+
+ /// <summary>
+ /// Adds the specified column to the specified table.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="column">The column.</param>
+ /// <returns></returns>
+ DataColumn AddColumn(DataTable table, DataColumn column);
+
+ /// <summary>
+ /// Gets the SQL command for <see cref="AddColumn(DataTable, DataColumn)"/>.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="column">The column.</param>
+ /// <returns></returns>
+ String GetAddColumnCommand(DataTable table, DataColumn column);
+
+ /// <summary>
+ /// Adds the specified row to the specified table.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="row">The row.</param>
+ void AddRow(DataTable table, DataRow row);
+
+ /// <summary>
+ /// Gets the SQL command for <see cref="AddRow(DataTable, DataRow)"/>.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="row">The row.</param>
+ /// <returns></returns>
+ String GetAddRowCommand(DataTable table, DataRow row);
+
+ /// <summary>
+ /// Updates the matching row by the row GUID.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="row">The row.</param>
+ void UpdateRow(DataTable table, DataRow row);
+
+ /// <summary>
+ /// Gets the SQL command for <see cref="UpdateRow(DataTable, DataRow)"/>.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="row">The row.</param>
+ /// <returns></returns>
+ String GetUpdateRowCommand(DataTable table, DataRow row);
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Synchronization/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.Synchronization/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..1221e84be
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Tango - DataBase Synchronization Components")]
+[assembly: ComVisible(false)] \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.Synchronization/SqlDataBaseComparer.cs b/Software/Visual_Studio/Tango.Synchronization/SqlDataBaseComparer.cs
new file mode 100644
index 000000000..07c94045b
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/SqlDataBaseComparer.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Synchronization
+{
+ /// <summary>
+ /// Represents an <see cref="ISqlDataBase"/> synchronization engine.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
+ public class SqlDataBaseComparer : IDisposable
+ {
+ /// <summary>
+ /// Gets the master SQL.
+ /// </summary>
+ public ISqlDataBase MasterSQL { get; private set; }
+
+ /// <summary>
+ /// Gets the slave SQL.
+ /// </summary>
+ public ISqlDataBase SlaveSQL { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SqlDataBaseComparer"/> class.
+ /// </summary>
+ /// <param name="masterSQL">The master SQL.</param>
+ /// <param name="slaveSQL">The slave SQL.</param>
+ public SqlDataBaseComparer(ISqlDataBase masterSQL, ISqlDataBase slaveSQL)
+ {
+ MasterSQL = masterSQL;
+ SlaveSQL = slaveSQL;
+ }
+
+ /// <summary>
+ /// Compares the master and slave SQL data base and returns a collection of <see cref="SqlDiff"/>.
+ /// </summary>
+ /// <returns></returns>
+ public List<SqlDiff> Compare()
+ {
+ List<SqlDiff> diffs = new List<SqlDiff>();
+
+ foreach (var masterTable in MasterSQL.Tables)
+ {
+ var slaveTable = SlaveSQL.Tables.SingleOrDefault(x => x.TableName == masterTable.TableName); //Get matching slave table on slave db.
+
+ if (slaveTable == null) //Table not found on slave.
+ {
+ //Clone table from slave db to master db including records!
+ diffs.Add(new SqlDiff(SqlDiffAction.AddTableToSlave, String.Format("Add table {0} to slave", masterTable.TableName), () => SlaveSQL.CloneTableFrom(MasterSQL, masterTable), SlaveSQL.GetCloneTableFromCommand(MasterSQL, masterTable)));
+ continue;
+ }
+
+ foreach (DataColumn masterColumn in masterTable.Columns)
+ {
+ var slaveColumn = slaveTable.Columns[masterColumn.ColumnName]; //Get matching slave column on slave table.
+
+ if (slaveColumn == null) //Slave column not found.
+ {
+ //Add column to slave table.
+ diffs.Add(new SqlDiff(SqlDiffAction.AddColumnToSlave, String.Format("Add column {0} to slave table", masterColumn.ColumnName), () => SlaveSQL.AddColumn(masterTable, masterColumn), SlaveSQL.GetAddColumnCommand(masterTable, masterColumn)));
+ }
+ }
+
+ List<DataRow> addToSlave = new List<DataRow>();
+ List<DataRow> updateSlave = new List<DataRow>();
+ List<DataRow> addToMaster = new List<DataRow>();
+ List<DataRow> updateMaster = new List<DataRow>();
+
+ foreach (DataRow masterRow in masterTable.Rows)
+ {
+ String guid = masterRow.Field<String>(Constants.GUID);
+
+ //Get Matching slave row.
+ DataRow slaveRow = slaveTable.AsEnumerable().SingleOrDefault(x => x.Field<String>(Constants.GUID) == guid);
+
+ if (slaveRow != null)
+ {
+ DateTime masterDate = masterRow.Field<DateTime>(Constants.LAST_UPDATED);
+ DateTime slaveDate = slaveRow.Field<DateTime>(Constants.LAST_UPDATED);
+
+ if (masterDate > slaveDate)
+ {
+ updateSlave.Add(masterRow);
+ }
+ else if (slaveDate > masterDate)
+ {
+ updateMaster.Add(slaveRow);
+ }
+ }
+ else
+ {
+ addToSlave.Add(masterRow);
+ }
+ }
+
+ foreach (DataRow slaveRow in slaveTable.Rows)
+ {
+ String guid = slaveRow.Field<String>(Constants.GUID);
+
+ //Get Matching slave row.
+ DataRow masterRow = masterTable.AsEnumerable().SingleOrDefault(x => x.Field<String>(Constants.GUID) == guid);
+
+ if (masterRow == null)
+ {
+ addToMaster.Add(slaveRow);
+ }
+ }
+
+ foreach (var row in addToSlave)
+ {
+ diffs.Add(new SqlDiff(SqlDiffAction.AddRowToSlave, String.Format("Add row to slave table {0}", slaveTable.TableName), () => SlaveSQL.AddRow(slaveTable, row), SlaveSQL.GetAddRowCommand(slaveTable, row)));
+ }
+
+ foreach (var row in addToMaster)
+ {
+ diffs.Add(new SqlDiff(SqlDiffAction.AddRowToMaster, String.Format("Add row to master table {0}", masterTable.TableName), () => MasterSQL.AddRow(masterTable, row), MasterSQL.GetAddRowCommand(masterTable, row)));
+ }
+
+ foreach (var row in updateSlave)
+ {
+ diffs.Add(new SqlDiff(SqlDiffAction.UpdateRowInSlave, String.Format("Update row in slave table {0}", slaveTable.TableName), () => SlaveSQL.UpdateRow(slaveTable, row), SlaveSQL.GetUpdateRowCommand(slaveTable, row)));
+ }
+
+ foreach (var row in updateMaster)
+ {
+ diffs.Add(new SqlDiff(SqlDiffAction.UpdateRowInMaster, String.Format("Update row in master table {0}", masterTable.TableName), () => MasterSQL.UpdateRow(masterTable, row), MasterSQL.GetUpdateRowCommand(masterTable, row)));
+ }
+ }
+
+ return diffs;
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ MasterSQL.Dispose();
+ SlaveSQL.Dispose();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Synchronization/SqlDiff.cs b/Software/Visual_Studio/Tango.Synchronization/SqlDiff.cs
new file mode 100644
index 000000000..b5a274cdc
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/SqlDiff.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Synchronization
+{
+ /// <summary>
+ /// Represents an <see cref="SqlDataBaseComparer.Compare"/> result.
+ /// </summary>
+ public class SqlDiff
+ {
+ private Action _action;
+
+ /// <summary>
+ /// Gets the difference action.
+ /// </summary>
+ public SqlDiffAction Action { get; private set; }
+
+ /// <summary>
+ /// Gets the difference description.
+ /// </summary>
+ public String Description { get; private set; }
+
+ /// <summary>
+ /// Gets the <see cref="Commit"/> command.
+ /// </summary>
+ public String Command { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SqlDiff"/> class.
+ /// </summary>
+ /// <param name="dataBaseAction">The data base action.</param>
+ /// <param name="description">The description.</param>
+ /// <param name="action">The action.</param>
+ /// <param name="command">The command.</param>
+ public SqlDiff(SqlDiffAction dataBaseAction, String description, Action action, String command)
+ {
+ Action = dataBaseAction;
+ Description = description;
+ _action = action;
+ Command = command;
+ }
+
+ /// <summary>
+ /// Commits the difference to the target DB.
+ /// </summary>
+ public void Commit()
+ {
+ _action();
+ }
+
+ /// <summary>
+ /// Returns a <see cref="System.String" /> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String" /> that represents this instance.
+ /// </returns>
+ public override string ToString()
+ {
+ return Command;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Synchronization/SqlDiffAction.cs b/Software/Visual_Studio/Tango.Synchronization/SqlDiffAction.cs
new file mode 100644
index 000000000..798be78d8
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/SqlDiffAction.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Synchronization
+{
+ /// <summary>
+ /// Represents the type of <see cref="SqlDiff"/>.
+ /// </summary>
+ public enum SqlDiffAction
+ {
+ /// <summary>
+ /// Add table to slave
+ /// </summary>
+ AddTableToSlave,
+ /// <summary>
+ /// Add column to slave
+ /// </summary>
+ AddColumnToSlave,
+ /// <summary>
+ /// Add row to slave
+ /// </summary>
+ AddRowToSlave,
+ /// <summary>
+ /// Add row to master
+ /// </summary>
+ AddRowToMaster,
+ /// <summary>
+ /// Update row in slave
+ /// </summary>
+ UpdateRowInSlave,
+ /// <summary>
+ /// Update row in master
+ /// </summary>
+ UpdateRowInMaster,
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Synchronization/SqliteDataBase.cs b/Software/Visual_Studio/Tango.Synchronization/SqliteDataBase.cs
new file mode 100644
index 000000000..3802232e0
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/SqliteDataBase.cs
@@ -0,0 +1,436 @@
+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;
+
+namespace Tango.Synchronization
+{
+ /// <summary>
+ /// Represents an SQLite database adapter used for synchronization by <see cref="SqlDataBaseComparer"/>.
+ /// </summary>
+ /// <seealso cref="Tango.Synchronization.ISqlDataBase" />
+ public class SqliteDataBase : ISqlDataBase
+ {
+ private SQLiteConnection _connection;
+
+ /// <summary>
+ /// Gets the database source URL/File.
+ /// </summary>
+ public String Source { get; private set; }
+
+ /// <summary>
+ /// Gets the database tables collection.
+ /// </summary>
+ public List<DataTable> Tables { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SqliteDataBase"/> class.
+ /// </summary>
+ /// <param name="filePath">The file path.</param>
+ 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();
+
+ InitializeTables();
+ }
+
+ /// <summary>
+ /// Initializes the tables.
+ /// </summary>
+ private void InitializeTables()
+ {
+ Tables = new List<DataTable>();
+
+ 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));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Loads the table.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <returns></returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Clones a table from another database into this database.
+ /// </summary>
+ /// <param name="otherDB">The other database.</param>
+ /// <param name="otherTable">The other table.</param>
+ /// <returns></returns>
+ public DataTable CloneTableFrom(ISqlDataBase 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;
+ }
+
+ /// <summary>
+ /// Gets the SQL command for <see cref="CloneTableFrom(ISqlDataBase, DataTable)" />.
+ /// </summary>
+ /// <param name="otherDB">The other database.</param>
+ /// <param name="otherTable">The other table.</param>
+ /// <returns></returns>
+ public String GetCloneTableFromCommand(ISqlDataBase 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;
+ }
+
+ /// <summary>
+ /// Adds the specified column to the specified table.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="column">The column.</param>
+ /// <returns></returns>
+ 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} {6}",
+ 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];
+ }
+
+ /// <summary>
+ /// Gets the SQL command for <see cref="AddColumn(DataTable, DataColumn)" />.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="column">The column.</param>
+ /// <returns></returns>
+ public String GetAddColumnCommand(DataTable table, DataColumn column)
+ {
+ String cmd = String.Format("ALTER TABLE {0} ADD COLUMN {1} {2} {3} {4} {5} {6}",
+ 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;
+ }
+
+ /// <summary>
+ /// Adds the specified row to the specified table.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="row">The row.</param>
+ public void AddRow(DataTable table, DataRow row)
+ {
+ var insertCommand = _connection.CreateCommand();
+
+ List<String> values = new List<string>();
+
+ for (int i = 0; i < row.ItemArray.Length; i++)
+ {
+ String value = row.ItemArray[i].ToString();
+
+ double num = 0;
+
+ 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[];
+ }
+
+ /// <summary>
+ /// Gets the SQL command for <see cref="AddRow(DataTable, DataRow)" />.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="row">The row.</param>
+ /// <returns></returns>
+ public String GetAddRowCommand(DataTable table, DataRow row)
+ {
+ List<String> values = new List<string>();
+
+ for (int i = 0; i < row.ItemArray.Length; i++)
+ {
+ String value = row.ItemArray[i].ToString();
+
+ double num = 0;
+
+ 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;
+ }
+
+ /// <summary>
+ /// Updates the matching row by the row GUID.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="row">The row.</param>
+ public void UpdateRow(DataTable table, DataRow row)
+ {
+ var updateCommand = _connection.CreateCommand();
+
+ List<String> values = new List<string>();
+
+ for (int i = 0; i < row.ItemArray.Length; i++)
+ {
+ String value = row.ItemArray[i].ToString();
+
+ double num = 0;
+
+ 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[];
+ }
+
+ /// <summary>
+ /// Gets the SQL command for <see cref="UpdateRow(DataTable, DataRow)" />.
+ /// </summary>
+ /// <param name="table">The table.</param>
+ /// <param name="row">The row.</param>
+ /// <returns></returns>
+ public String GetUpdateRowCommand(DataTable table, DataRow row)
+ {
+ List<String> values = new List<string>();
+
+ for (int i = 0; i < row.ItemArray.Length; i++)
+ {
+ String value = row.ItemArray[i].ToString();
+
+ double num = 0;
+
+ 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;
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ _connection.Close();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Synchronization/Tango.Synchronization.csproj b/Software/Visual_Studio/Tango.Synchronization/Tango.Synchronization.csproj
new file mode 100644
index 000000000..da8054956
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/Tango.Synchronization.csproj
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{7ADA4E86-CAD7-4968-A210-3A8A9E5153AB}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Tango.Synchronization</RootNamespace>
+ <AssemblyName>Tango.Synchronization</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <NuGetPackageImportStamp>
+ </NuGetPackageImportStamp>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+ <HintPath>..\packages\EntityFramework.6.0.0\lib\net45\EntityFramework.dll</HintPath>
+ </Reference>
+ <Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+ <HintPath>..\packages\EntityFramework.6.0.0\lib\net45\EntityFramework.SqlServer.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.DataAnnotations" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Data.SQLite, Version=1.0.106.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Data.SQLite.Core.1.0.106.0\lib\net45\System.Data.SQLite.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Data.SQLite.EF6, Version=1.0.106.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Data.SQLite.EF6.1.0.106.0\lib\net45\System.Data.SQLite.EF6.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Data.SQLite.Linq, Version=1.0.106.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Data.SQLite.Linq.1.0.106.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\Versioning\GlobalVersionInfo.cs">
+ <Link>GlobalVersionInfo.cs</Link>
+ </Compile>
+ <Compile Include="Constants.cs" />
+ <Compile Include="ExtensionMethods.cs" />
+ <Compile Include="ISqlDataBase.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="SqlDataBaseComparer.cs" />
+ <Compile Include="SqlDiff.cs" />
+ <Compile Include="SqlDiffAction.cs" />
+ <Compile Include="SqliteDataBase.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config" />
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets')" />
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+ <PropertyGroup>
+ <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+ </PropertyGroup>
+ <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets'))" />
+ </Target>
+</Project> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.Synchronization/packages.config b/Software/Visual_Studio/Tango.Synchronization/packages.config
new file mode 100644
index 000000000..1c70a6138
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Synchronization/packages.config
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="EntityFramework" version="6.0.0" targetFramework="net45" />
+ <package id="System.Data.SQLite" version="1.0.106.0" targetFramework="net45" />
+ <package id="System.Data.SQLite.Core" version="1.0.106.0" targetFramework="net45" />
+ <package id="System.Data.SQLite.EF6" version="1.0.106.0" targetFramework="net45" />
+ <package id="System.Data.SQLite.Linq" version="1.0.106.0" targetFramework="net45" />
+</packages> \ No newline at end of file