using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using remote = Tango.DAL.Remote.DB;
using local = Tango.DAL.Local.DB;
using System.Data.Entity;
using Tango.Logging;
using System.Collections;
namespace Tango.Synchronization.Remote
{
///
/// Represents a Remote database to SQLite database comparison engine.
///
///
public class RemoteDBComparer : IDBComparer
{
private remote.RemoteDB _remoteDB;
private local.LocalDB _localDB;
private String _machineSerial;
private List _diffs;
private LogManager LogManager = LogManager.Default;
///
/// Used to notify about the comparer progress using text messages.
///
public event EventHandler Progress;
///
/// Initializes a new instance of the class.
///
/// The remote database.
/// The local database.
/// The machine serial number.
public RemoteDBComparer(remote.RemoteDB remoteDB, local.LocalDB localDB, String machineSerial)
{
_machineSerial = machineSerial;
_remoteDB = remoteDB;
_localDB = localDB;
}
///
/// Performs a database comparison and returns a set of differences.
///
///
/// Could not locate machine on remote server " + _machineSerial
public List Compare()
{
_diffs = new List();
OnProgress(LogManager.Log("Performing SQL Server <=> SQLite database comparison..."));
OnProgress(LogManager.Log("Loading SQLite database to memory..."));
OnProgress(LogManager.Log("Querying all local addresses..."));
var local_addresses = _localDB.ADDRESSES.ToList();
OnProgress(LogManager.Log("Querying all local configurations..."));
var local_configurations = _localDB.CONFIGURATIONS.ToList();
OnProgress(LogManager.Log("Querying all local configuration dispensers..."));
var local_ids_packs = _localDB.IDS_PACKS.ToList();
OnProgress(LogManager.Log("Querying all local contacts..."));
var local_contacts = _localDB.CONTACTS.ToList();
OnProgress(LogManager.Log("Querying all local machines..."));
var local_machines = _localDB.MACHINES.Where(x => x.SERIAL_NUMBER == _machineSerial).ToList();
OnProgress(LogManager.Log("Querying all local machines configurations..."));
var local_machines_configurations = _localDB.MACHINES_CONFIGURATIONS.ToList();
OnProgress(LogManager.Log("Querying all local machines events..."));
var local_machines_events = _localDB.MACHINES_EVENTS.ToList();
OnProgress(LogManager.Log("Querying all local organizations..."));
var local_organizations = _localDB.ORGANIZATIONS.ToList();
OnProgress(LogManager.Log("Querying all local users..."));
var local_users = _localDB.USERS.ToList();
OnProgress(LogManager.Log("Querying all local users roles..."));
var local_users_roles = _localDB.USERS_ROLES.ToList();
OnProgress(LogManager.Log("Querying all local jobs..."));
var local_jobs = _localDB.JOBS.ToList();
OnProgress(LogManager.Log("Querying all local job runs..."));
var local_job_runs = _localDB.JOB_RUNS.ToList();
OnProgress(LogManager.Log("Querying all local segments..."));
var local_segments = _localDB.SEGMENTS.ToList();
OnProgress(LogManager.Log("Querying all local brush stops..."));
var local_brush_stops = _localDB.BRUSH_STOPS.ToList();
//LogManager.Log("Querying all CATS...");
//var local_cats = _localDB.CATS.ToList();
OnProgress(LogManager.Log("Loading SQL Server database to memory..."));
List guids = new List();
OnProgress(LogManager.Log("Querying all remote machines..."));
var remote_machines = _remoteDB.MACHINES.Where(x => x.SERIAL_NUMBER == _machineSerial).ToList();
if (remote_machines.Count == 0)
{
throw LogManager.Log(new DataBaseComparisonException("Could not locate machine on remote server " + _machineSerial));
}
OnProgress(LogManager.Log("Querying all remote organizations..."));
guids = remote_machines.Select(x => x.ORGANIZATION_GUID).ToList();
var remote_organizations = _remoteDB.ORGANIZATIONS.Where(x => guids.Contains(x.GUID)).ToList();
OnProgress(LogManager.Log("Querying all remote machines configurations..."));
guids = remote_machines.Select(x => x.GUID).ToList();
//var remote_machines_configurations = _remoteDB.MACHINES_CONFIGURATIONS.Where(x => guids.Contains(x.MACHINE_GUID)).ToList();
//var remote_configurations = remote_machines_configurations.Select(x => x.CONFIGURATION).ToList();
OnProgress(LogManager.Log("Querying all remote machines events..."));
var remote_machines_events = _remoteDB.MACHINES_EVENTS.Where(x => guids.Contains(x.MACHINE_GUID)).ToList();
OnProgress(LogManager.Log("Querying all remote users..."));
guids = remote_organizations.Select(x => x.GUID).ToList();
var remote_users = _remoteDB.USERS.Where(x => guids.Contains(x.ORGANIZATION_GUID)).ToList();
OnProgress(LogManager.Log("Querying all remote users roles..."));
guids = remote_users.Select(x => x.GUID).ToList();
var remote_users_roles = _remoteDB.USERS_ROLES.Where(x => guids.Contains(x.USER_GUID)).ToList();
OnProgress(LogManager.Log("Querying all remote addresses..."));
var remote_addresses = remote_users.Select(x => x.ADDRESS).ToList();
remote_addresses.AddRange(remote_machines.Select(x => x.ORGANIZATION).Select(x => x.ADDRESS));
remote_addresses = remote_addresses.Distinct().ToList();
OnProgress(LogManager.Log("Querying all remote contacts..."));
var remote_contacts = remote_users.Select(x => x.CONTACT).ToList();
remote_contacts.AddRange(remote_machines.Select(x => x.ORGANIZATION).Select(x => x.CONTACT));
remote_contacts = remote_contacts.Distinct().ToList();
OnProgress(LogManager.Log("Querying all remote IDS Packs..."));
//guids = remote_configurations.Select(x => x.GUID).ToList();
var remote_ids_packs = _remoteDB.IDS_PACKS.Where(x => guids.Contains(x.CONFIGURATION_GUID)).ToList();
OnProgress(LogManager.Log("Querying all remote dispenser types..."));
//guids = remote_ids_packs.Select(x => x.DISPENSER_TYPE_GUID).ToList();
var remote_dispensers_types = _remoteDB.DISPENSER_TYPES.Where(x => guids.Contains(x.GUID)).ToList();
OnProgress(LogManager.Log("Querying all remote cartridge types..."));
guids = remote_ids_packs.Select(x => x.CARTRIDGE_TYPE_GUID).ToList();
var remote_cartridges_types = _remoteDB.CARTRIDGE_TYPES.Where(x => guids.Contains(x.GUID)).ToList();
OnProgress(LogManager.Log("Querying all remote jobs..."));
var remote_jobs = remote_machines.SelectMany(x => x.JOBS).ToList();
OnProgress(LogManager.Log("Querying all remote job runs..."));
//var remote_jobs_runs = remote_jobs.SelectMany(x => x.JOB_RUNS).ToList();
OnProgress(LogManager.Log("Querying all remote segments..."));
var remote_segments = remote_jobs.SelectMany(x => x.SEGMENTS).ToList();
OnProgress(LogManager.Log("Querying all remote brush stops..."));
var remote_brush_stops = remote_segments.SelectMany(x => x.BRUSH_STOPS).ToList();
//LogManager.Log("Querying all remote CATS...");
//guids = remote_machines.Select(x => x.GUID).ToList();
//var remote_cats = _remoteDB.CATS.Where(x => guids.Contains(x.MACHINE_GUID)).ToList();
foreach (var config in _remoteDB.SYNC_CONFIGURATIONS.Where(x => (SyncConfiguration)x.SYNC_TYPE == SyncConfiguration.OverwriteLocal))
{
OverrideTable(config);
}
OnProgress(LogManager.Log("Comparing addresses"));
CompareCollections(remote_addresses, local_addresses, _remoteDB.ADDRESSES, _localDB.ADDRESSES);
OnProgress(LogManager.Log("Comparing configurations"));
//CompareCollections(remote_configurations, local_configurations, _remoteDB.CONFIGURATIONS, _localDB.CONFIGURATIONS);
OnProgress(LogManager.Log("Comparing configurations dispensers"));
CompareCollections(remote_ids_packs, local_ids_packs, _remoteDB.IDS_PACKS, _localDB.IDS_PACKS);
OnProgress(LogManager.Log("Comparing contacts"));
CompareCollections(remote_contacts, local_contacts, _remoteDB.CONTACTS, _localDB.CONTACTS);
OnProgress(LogManager.Log("Comparing machines"));
CompareCollections(remote_machines, local_machines, _remoteDB.MACHINES, _localDB.MACHINES);
OnProgress(LogManager.Log("Comparing machines configuration"));
//CompareCollections(remote_machines_configurations, local_machines_configurations, _remoteDB.MACHINES_CONFIGURATIONS, _localDB.MACHINES_CONFIGURATIONS);
OnProgress(LogManager.Log("Comparing machines events"));
CompareCollections(remote_machines_events, local_machines_events, _remoteDB.MACHINES_EVENTS, _localDB.MACHINES_EVENTS);
OnProgress(LogManager.Log("Comparing organizations"));
CompareCollections(remote_organizations, local_organizations, _remoteDB.ORGANIZATIONS, _localDB.ORGANIZATIONS);
OnProgress(LogManager.Log("Comparing users"));
CompareCollections(remote_users, local_users, _remoteDB.USERS, _localDB.USERS);
OnProgress(LogManager.Log("Comparing users roles"));
CompareCollections(remote_users_roles, local_users_roles, _remoteDB.USERS_ROLES, _localDB.USERS_ROLES);
OnProgress(LogManager.Log("Comparing jobs"));
CompareCollections(remote_jobs, local_jobs, _remoteDB.JOBS, _localDB.JOBS);
OnProgress(LogManager.Log("Comparing job runs"));
//CompareCollections(remote_jobs_runs, local_job_runs, _remoteDB.JOB_RUNS, _localDB.JOB_RUNS);
OnProgress(LogManager.Log("Comparing segments"));
CompareCollections(remote_segments, local_segments, _remoteDB.SEGMENTS, _localDB.SEGMENTS);
OnProgress(LogManager.Log("Comparing brush stops"));
CompareCollections(remote_brush_stops, local_brush_stops, _remoteDB.BRUSH_STOPS, _localDB.BRUSH_STOPS);
//LogManager.Log("Comparing cats");
//CompareCollections(remote_cats, local_cats, _remoteDB.CATS, _localDB.CATS);
OnProgress(LogManager.Log("Comparison done!"));
return _diffs;
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose()
{
_remoteDB.Dispose();
_localDB.Dispose();
}
///
/// Copies source entity properties to the destination entity.
///
/// The source entity.
/// The destination entity.
///
private void CopyEntity(Object source, Object destination)
{
foreach (var prop in source.GetType().GetProperties().Where(x => x.PropertyType.IsPrimitive || x.PropertyType == typeof(String) || x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(byte[])))
{
if (destination.GetType().GetProperty(prop.Name) == null)
{
throw LogManager.Log(new InvalidOperationException(String.Format("Property '{0}' not found on destination table '{1}'.", prop.Name, destination.GetType().Name)));
}
if (prop.PropertyType == typeof(Int64))
{
destination.GetType().GetProperty(prop.Name).SetValue(destination, Convert.ToInt32((Int64)prop.GetValue(source)));
}
else
{
destination.GetType().GetProperty(prop.Name).SetValue(destination, prop.GetValue(source));
}
}
}
///
/// Compares a collection of source entities to a collection of destination entities.
///
/// The type of the aster.
/// The type of the lave.
/// The master collection.
/// The slave collection.
/// The master set.
/// The slave set.
private void CompareCollections(List masterCollection, List slaveCollection, DbSet masterSet, DbSet slaveSet) where Master : class where Slave : class
{
var slaveProp = typeof(Slave).GetProperty("GUID");
var masterProp = typeof(Master).GetProperty("GUID");
List compared = new List();
foreach (var masterRow in masterCollection)
{
Slave slaveRow = slaveCollection.SingleOrDefault(x => slaveProp.GetValue(x).ToString() == masterProp.GetValue(masterRow).ToString());
CompareEntities(masterRow, slaveRow, masterSet, slaveSet);
if (slaveRow != null)
{
compared.Add(slaveRow);
}
}
foreach (var slaveRow in slaveCollection.Where(x => !compared.Contains(x)))
{
CompareEntities(masterCollection.SingleOrDefault(x => masterProp.GetValue(x).ToString() == slaveProp.GetValue(slaveRow).ToString()), slaveRow, masterSet, slaveSet);
}
}
///
/// Overrides the table by the specified synchronization configuration.
///
/// The configuration.
private void OverrideTable(remote.SYNC_CONFIGURATIONS config)
{
OnProgress(LogManager.Log("Generating table override difference for " + config.TABLE_NAME + "..."));
var master = _remoteDB.GetType().GetProperty(config.TABLE_NAME).GetValue(_remoteDB) as IEnumerable;
var slave = _localDB.GetType().GetProperty(config.TABLE_NAME).GetValue(_localDB) as IEnumerable;
_diffs.Add(new Diff(DiffAction.ReplaceTableDataInSlave, "Override all rows on slave table " + config.TABLE_NAME, () =>
{
OnProgress(LogManager.Log("Overwriting slave table " + config.TABLE_NAME + "..."));
_localDB.Database.ExecuteSqlCommand("DELETE FROM " + config.TABLE_NAME + ";");
foreach (var entity in master)
{
var newRow = slave.GetType().GetMethods().Where(x => x.Name == "Create").First().Invoke(slave, new object[] { });
CopyEntity(entity, newRow);
slave.GetType().GetMethod("Add").Invoke(slave, new object[] { newRow });
}
}, null));
}
///
/// Compares the specified entities.
///
/// The type of the master.
/// The type of the slave.
/// The master.
/// The slave.
private void CompareEntities(Master master, Slave slave) where Master : class where Slave : class
{
Diff diff = null;
DateTime masterDate = (DateTime)master.GetType().GetProperty("LAST_UPDATED").GetValue(master);
DateTime slaveDate = (DateTime)slave.GetType().GetProperty("LAST_UPDATED").GetValue(slave);
if (masterDate > slaveDate)
{
diff = new Diff(DiffAction.UpdateRowInSlave, "Update row in slave table " + typeof(Master).Name, () =>
{
OnProgress(LogManager.Log("Updating row in slave table " + typeof(Master).Name));
CopyEntity(master, slave);
}, null);
}
else if (slaveDate > masterDate)
{
diff = new Diff(DiffAction.UpdateRowInMaster, "Update row in master table " + typeof(Master).Name, () =>
{
OnProgress(LogManager.Log("Updating row in master table " + typeof(Master).Name));
CopyEntity(slave, master);
}, null);
}
if (diff != null)
{
_diffs.Add(diff);
}
}
///
/// Compares the entities.
///
/// The type of the master.
/// The type of the slave.
/// The master.
/// The slave.
/// The master set.
/// The slave set.
private void CompareEntities(Master master, Slave slave, DbSet masterSet, DbSet slaveSet) where Master : class where Slave : class
{
bool skipAddToSlave = false;
if (slave == null && _remoteDB.SYNC_CONFIGURATIONS.ToList().Exists(x => (SyncConfiguration)x.SYNC_TYPE == SyncConfiguration.SynchronizeToRemote && x.TABLE_NAME.SingularizeMVC() == typeof(Master).Name))
{
skipAddToSlave = true;
}
if (slave == null && !skipAddToSlave)
{
_diffs.Add(new Diff(DiffAction.AddRowToSlave, "Add row to slave table " + typeof(Master).Name, () =>
{
OnProgress(LogManager.Log("Adding row to slave table " + typeof(Master).Name));
Slave newRow = slaveSet.Create();
CopyEntity(master, newRow);
slaveSet.Add(newRow);
}, null));
return;
}
if (master == null)
{
_diffs.Add(new Diff(DiffAction.AddRowToMaster, "Add row to master table " + typeof(Master).Name, () =>
{
OnProgress(LogManager.Log("Adding row to master table " + typeof(Master).Name));
Master newRow = masterSet.Create();
CopyEntity(slave, newRow);
masterSet.Add(newRow);
}, null));
return;
}
if (slave != null && master != null)
{
CompareEntities(master, slave);
}
}
///
/// Raises the event.
///
///
protected virtual void OnProgress(String message)
{
Progress?.Invoke(this, message);
}
}
}