using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.Logging; namespace Tango.Synchronization.Local { /// /// Represents an synchronization engine. /// /// public class LocalDBComparer : IDBComparer { private LogManager LogManager = LogManager.Default; /// /// Used to notify about the comparer progress using text messages. /// public event EventHandler Progress; /// /// Gets the master SQL. /// public ILocalDataBase MasterSQL { get; private set; } /// /// Gets the slave SQL. /// public ILocalDataBase SlaveSQL { get; private set; } /// /// Initializes a new instance of the class. /// /// The master SQL. /// The slave SQL. public LocalDBComparer(ILocalDataBase masterSQL, ILocalDataBase slaveSQL) { MasterSQL = masterSQL; SlaveSQL = slaveSQL; } /// /// Compares the master and slave SQL data base and returns a collection of . /// /// public List Compare() { OnProgress(LogManager.Log("Comparing databases " + Path.GetFileName(MasterSQL.Source) + " <=> " + Path.GetFileName(SlaveSQL.Source))); OnProgress(LogManager.Log("Loading master tables...")); MasterSQL.LoadTables(); OnProgress(LogManager.Log("Loading slave tables...")); SlaveSQL.LoadTables(); List diffs = new List(); DataTable sync_table = MasterSQL.Tables.Single(x => x.TableName == Constants.SYNC_CONFIGURATIONS_TABLE_NAME); List sync_tables = new List(); List overwrite_tables = new List(); foreach (var row in sync_table.AsEnumerable()) { SyncConfiguration config = (SyncConfiguration)row.Field(Constants.SYNC_CONFIGURATION_SYNC_TYPE_COLUMN); if (config == SyncConfiguration.Synchronize || config == SyncConfiguration.SynchronizeToRemote) { sync_tables.Add(MasterSQL.Tables.Single(x => x.TableName == row.Field(Constants.SYNC_CONFIGURATION_TABLE_NAME_COLUMN))); } else { overwrite_tables.Add(MasterSQL.Tables.Single(x => x.TableName == row.Field(Constants.SYNC_CONFIGURATION_TABLE_NAME_COLUMN))); } } foreach (var masterTable in overwrite_tables) { OnProgress(LogManager.Log("Generating table overwrite difference for table " + masterTable.TableName)); 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. { OnProgress(LogManager.Log("Table not found on slave, adding difference.")); //Clone table from slave db to master db including records! diffs.Add(new Diff(DiffAction.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) { OnProgress(LogManager.Log("Searching for column " + masterColumn.ColumnName + " on slave...")); var slaveColumn = slaveTable.Columns[masterColumn.ColumnName]; //Get matching slave column on slave table. if (slaveColumn == null) //Slave column not found. { OnProgress(LogManager.Log("Column not found on slave, adding difference.")); //Add column to slave table. diffs.Add(new Diff(DiffAction.AddColumnToSlave, String.Format("Add column {0} to slave table", masterColumn.ColumnName), () => SlaveSQL.AddColumn(masterTable, masterColumn), SlaveSQL.GetAddColumnCommand(masterTable, masterColumn))); } OnProgress(LogManager.Log("Column found.")); } diffs.Add(new Diff(DiffAction.ReplaceTableDataInSlave, "Replace all rows on slave table " + masterTable.TableName, () => { SlaveSQL.ReplaceTableData(MasterSQL, masterTable); }, SlaveSQL.GetReplaceTableDataCommand(MasterSQL, masterTable))); } foreach (var masterTable in sync_tables) { OnProgress(LogManager.Log("Comparing table " + masterTable.TableName)); OnProgress(LogManager.Log("Searching table " + masterTable.TableName + " on slave...")); 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. { OnProgress(LogManager.Log("Table not found on slave, adding difference.")); //Clone table from slave db to master db including records! diffs.Add(new Diff(DiffAction.AddTableToSlave, String.Format("Add table {0} to slave", masterTable.TableName), () => SlaveSQL.CloneTableFrom(MasterSQL, masterTable), SlaveSQL.GetCloneTableFromCommand(MasterSQL, masterTable))); continue; } OnProgress(LogManager.Log("Table found, comparing columns...")); foreach (DataColumn masterColumn in masterTable.Columns) { OnProgress(LogManager.Log("Searching for column " + masterColumn.ColumnName + " on slave...")); var slaveColumn = slaveTable.Columns[masterColumn.ColumnName]; //Get matching slave column on slave table. if (slaveColumn == null) //Slave column not found. { OnProgress(LogManager.Log("Column not found on slave, adding difference.")); //Add column to slave table. diffs.Add(new Diff(DiffAction.AddColumnToSlave, String.Format("Add column {0} to slave table", masterColumn.ColumnName), () => SlaveSQL.AddColumn(masterTable, masterColumn), SlaveSQL.GetAddColumnCommand(masterTable, masterColumn))); } OnProgress(LogManager.Log("Column found.")); } List addToSlave = new List(); List updateSlave = new List(); List addToMaster = new List(); List updateMaster = new List(); OnProgress(LogManager.Log("Comparing rows...")); int count = 0; foreach (DataRow masterRow in masterTable.Rows) { OnProgress(LogManager.Log("Comparing row " + count++)); String guid = masterRow.Field(Constants.GUID); OnProgress(LogManager.Log("Searching for row with GUID " + guid + " on slave table...")); //Get Matching slave row. DataRow slaveRow = slaveTable.AsEnumerable().SingleOrDefault(x => x.Field(Constants.GUID) == guid); if (slaveRow != null) { OnProgress(LogManager.Log("Slave row found, comparing dates...")); DateTime masterDate = masterRow.Field(Constants.LAST_UPDATED); DateTime slaveDate = slaveRow.Field(Constants.LAST_UPDATED); if (masterDate > slaveDate) { OnProgress(LogManager.Log("Master => Slave Update " + masterDate.ToSQLiteDateString() + ", adding difference.")); updateSlave.Add(masterRow); } else if (slaveDate > masterDate) { OnProgress(LogManager.Log("Master <= Slave Update " + masterDate.ToSQLiteDateString() + ", adding difference.")); updateMaster.Add(slaveRow); } else { OnProgress(LogManager.Log("Master <=> Slave No Update.")); } } else { OnProgress(LogManager.Log("Slave row not found, adding difference.")); addToSlave.Add(masterRow); } } OnProgress(LogManager.Log("Done comparing rows...")); OnProgress(LogManager.Log("Searching for missing rows on master...")); foreach (DataRow slaveRow in slaveTable.Rows) { String guid = slaveRow.Field(Constants.GUID); OnProgress(LogManager.Log("Searching for row with GUID " + guid + " on master table...")); //Get Matching slave row. DataRow masterRow = masterTable.AsEnumerable().SingleOrDefault(x => x.Field(Constants.GUID) == guid); if (masterRow == null) { OnProgress(LogManager.Log("Master row not found, adding difference.")); addToMaster.Add(slaveRow); } else { OnProgress(LogManager.Log("Master row found.")); } } OnProgress(LogManager.Log("Done searching for missing rows on master...")); foreach (var row in addToSlave) { diffs.Add(new Diff(DiffAction.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 Diff(DiffAction.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 Diff(DiffAction.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 Diff(DiffAction.UpdateRowInMaster, String.Format("Update row in master table {0}", masterTable.TableName), () => MasterSQL.UpdateRow(masterTable, row), MasterSQL.GetUpdateRowCommand(masterTable, row))); } OnProgress(LogManager.Log("Done comparing table " + masterTable.TableName)); } OnProgress(LogManager.Log("Databases comparison completed.")); return diffs; } protected virtual void OnProgress(String message) { Progress?.Invoke(this, message); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { MasterSQL.Dispose(); SlaveSQL.Dispose(); } } }