using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.BL; using Tango.BL.ActionLogs; using Tango.BL.DTO; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.Core; using Tango.Core.DI; using Tango.DataStore; using Tango.DataStore.Editing; using Tango.DataStore.EF; using Tango.DataStore.Remote; using Tango.FSE.BL; using Tango.FSE.Common.Authentication; using Tango.FSE.Common.Connection; using Tango.FSE.Common.DataStore; namespace Tango.FSE.UI.DataStore { public class DefaultDataStoreProvider : ExtendedObject, IDataStoreProvider { private DataStoreModel _lastModel; private IMachineProvider MachineProvider { get; set; } [TangoInject] private FSEServicesContainer Services { get; set; } [TangoInject] protected IActionLogManager ActionLogManager { get; set; } [TangoInject] protected IAuthenticationProvider AuthenticationProvider { get; set; } private bool _acceptFirmwareChanges; public bool AcceptFirmwareChanges { get { return _acceptFirmwareChanges; } set { _acceptFirmwareChanges = value; RaisePropertyChangedAuto(); } } public DefaultDataStoreProvider(IMachineProvider machineProvider) { MachineProvider = machineProvider; MachineProvider.MachineConnected += MachineProvider_MachineConnected; } private void MachineProvider_MachineConnected(object sender, MachineConnectedEventArgs e) { _lastModel = null; if (MachineProvider.IsPPCAvailable) { LogManager.Log("Starting listening for data store changes..."); MachineProvider.MachineOperator.SendGenericContinuousRequest(new RemoteDataStoreStartListenRequest()).Subscribe((response) => { if (response.ChangeType != RemoteDataStoreChangeType.None) { LogManager.Log($"Data store change received for '{response.CollectionName}.{response.Item.Key}'..."); OnDataStoreItemChanged(response); } }, (ex) => { if (!(ex is Transport.TransporterDisconnectedException)) { LogManager.Log(ex, "Error occurred on data store changes listener."); } }, () => { //Nothing. }); } } private void OnDataStoreItemChanged(RemoteDataStoreStartListenResponse response) { try { if (_lastModel != null && AcceptFirmwareChanges) { DataStoreCollectionModel collectionModel = _lastModel.Collections.FirstOrDefault(x => x.Name == response.CollectionName); if (collectionModel == null) { collectionModel = new DataStoreCollectionModel(); collectionModel.Name = response.CollectionName; _lastModel.Collections.Add(collectionModel); } var remoteItem = response.Item; var itemModel = collectionModel.Items.FirstOrDefault(x => x.Guid == remoteItem.Guid); if (itemModel == null) { itemModel = DataStoreItemModel.FromLocalDataStoreItem(remoteItem, null); itemModel.ExistsOnMachine = true; collectionModel.Items.Add(itemModel); } else { itemModel.Value = remoteItem.Value; itemModel.Type = remoteItem.Type; itemModel.Date = remoteItem.Date; } } } catch (Exception ex) { LogManager.Log(ex, "Error occurred while processing a data store item remote change."); } } public Task GetDataStoreModel(String machineGuid) { return Task.Factory.StartNew(() => { DataStoreModel model = new DataStoreModel(); List globalItems = Services.DataStoreService.GetGlobalDataStoreItems().Result; List localItems = Services.DataStoreService.GetMachinelDataStoreItems(machineGuid).Result; List itemsModels = new List(); //Get machine items from db. foreach (var collection in localItems.GroupBy(x => x.CollectionName)) { DataStoreCollectionModel collectionModel = new DataStoreCollectionModel(); collectionModel.Name = collection.First().CollectionName; foreach (var item in collection) { GlobalDataStoreItem globalItem = globalItems.FirstOrDefault(x => x.CollectionName == item.CollectionName && x.Key == item.Key); if (globalItem != null) { globalItems.Remove(globalItem); } DataStoreItemModel itemModel = DataStoreItemModel.FromLocalDataStoreItem(item.ToDataStoreItem(), globalItem?.ToDataStoreItem()); collectionModel.Items.Add(itemModel); } model.Collections.Add(collectionModel); } //Get machine items from connected machine. if (MachineProvider.IsPPCAvailable && machineGuid == MachineProvider.Machine.Guid) { var response = MachineProvider.MachineOperator.SendGenericRequest(new RemoteDataStoreGetAllItemsRequest(), new Transport.TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(30) }).Result; foreach (var collection in response.Collections) { DataStoreCollectionModel collectionModel = model.Collections.FirstOrDefault(x => x.Name == collection.Name); if (collectionModel == null) { collectionModel = new DataStoreCollectionModel(); collectionModel.Name = collection.Name; model.Collections.Add(collectionModel); } foreach (var remoteItem in collection.Items) { var localItem = collectionModel.Items.FirstOrDefault(x => x.Key == remoteItem.Key); if (localItem != null) { localItem.ExistsOnMachine = true; } if (localItem != null && remoteItem.Date > localItem.Date) { localItem.Value = remoteItem.Value; localItem.OriginalValue = remoteItem.Value; localItem.Type = remoteItem.Type; localItem.Date = remoteItem.Date; } else if (localItem == null) { GlobalDataStoreItem globalItem = globalItems.FirstOrDefault(x => x.CollectionName == collection.Name && x.Key == remoteItem.Key); if (globalItem != null) { globalItems.Remove(globalItem); } DataStoreItemModel itemModel = DataStoreItemModel.FromLocalDataStoreItem(remoteItem, globalItem?.ToDataStoreItem()); itemModel.ExistsOnMachine = true; collectionModel.Items.Add(itemModel); } } } } //Get global items without overrides from db. foreach (var collection in globalItems.GroupBy(x => x.CollectionName)) { DataStoreCollectionModel collectionModel = model.Collections.FirstOrDefault(x => x.Name == collection.First().CollectionName); if (collectionModel == null) { collectionModel = new DataStoreCollectionModel(); collectionModel.Name = collection.First().CollectionName; model.Collections.Add(collectionModel); } foreach (var item in collection) { DataStoreItemModel itemModel = DataStoreItemModel.FromGlobalDataStoreItem(item.ToDataStoreItem()); collectionModel.Items.Add(itemModel); } } _lastModel = model; return model; }); } private class SaveModel { public String CollectionName { get; set; } public DataStoreItemModel Item { get; set; } } public Task UpdateDataStoreModel(DataStoreModel model, String machineGuid) { return Task.Factory.StartNew(() => { List globals = new List(); List locals = new List(); List deleted = new List(); List actionLogInserted = new List(); List> actionLogModified = new List>(); List actionLogDeleted = new List(); UpdateDataStoreRequest updateRequest = new UpdateDataStoreRequest(); foreach (var collection in model.Collections.Where(x => !x.IsDeleted)) { foreach (var item in collection.Items) { if (item.IsGlobal) { globals.Add(new SaveModel() { CollectionName = collection.Name, Item = item }); } else if (item.IsDeleted) { deleted.Add(new SaveModel() { CollectionName = collection.Name, Item = item }); } else if (item.HasDifference || (MachineProvider.IsPPCAvailable && !item.ExistsOnMachine)) { locals.Add(new SaveModel() { CollectionName = collection.Name, Item = item }); } } } using (ObservablesContext db = ObservablesContext.CreateDefault()) { var machine = db.Machines.SingleOrDefault(x => x.Guid == machineGuid); var allItems = db.DataStoreItems.Where(x => x.MachineGuid == machineGuid).ToList(); //Deleted collections foreach (var deletedCollection in model.Collections.Where(x => x.IsDeleted)) { foreach (var itemDb in allItems.ToList()) { if (itemDb.CollectionName == deletedCollection.Name) { itemDb.IsDeleted = true; itemDb.LastUpdated = DateTime.UtcNow; itemDb.IsSynchronized = false; updateRequest.ToDelete.Add(itemDb.Guid); actionLogDeleted.Add(DataStoreItemDTO.FromObservable(itemDb)); } } } //Deleted items foreach (var item in deleted) { var itemDb = allItems.FirstOrDefault(x => x.CollectionName == item.CollectionName && x.Key == item.Item.Key); if (itemDb != null) { itemDb.IsDeleted = true; itemDb.LastUpdated = DateTime.UtcNow; itemDb.IsSynchronized = false; updateRequest.ToDelete.Add(itemDb.Guid); try { actionLogDeleted.Add(DataStoreItemDTO.FromObservable(itemDb)); } catch { } } } //locals foreach (var item in locals) { DataStoreItem itemDb = allItems.FirstOrDefault(x => x.CollectionName == item.CollectionName && x.Key == item.Item.Key); if (itemDb == null) //new local item. { itemDb = new DataStoreItem(); itemDb.MachineGuid = machineGuid; itemDb.CollectionName = item.CollectionName; itemDb.Key = item.Item.Key; itemDb.LastUpdated = DateTime.UtcNow; itemDb.DataType = (int)item.Item.Type; itemDb.Value = EFDataStoreHelper.CreateBytes(item.Item.Type, item.Item.Value); db.DataStoreItems.Add(itemDb); updateRequest.ToUpsert.Add(DataStoreItemDTO.FromObservable(itemDb)); actionLogInserted.Add(DataStoreItemDTO.FromObservable(itemDb)); } else //update local item only if changed... { bool upsert = MachineProvider.IsPPCAvailable && !item.Item.ExistsOnMachine; var itemDbDTO = DataStoreItemDTO.FromObservable(itemDb); if (itemDb.IsDeleted) //restore if item was deleted.. { itemDb.IsDeleted = false; itemDb.LastUpdated = DateTime.UtcNow; itemDb.IsSynchronized = false; upsert = true; } //update item only if it has difference although "locals" already contains only differences. var bytes = EFDataStoreHelper.CreateBytes(item.Item.Type, item.Item.Value); if (itemDb.DataType != (int)item.Item.Type || !Enumerable.SequenceEqual(itemDb.Value, bytes)) { itemDb.DataType = (int)item.Item.Type; itemDb.Value = bytes; itemDb.LastUpdated = DateTime.UtcNow; itemDb.IsSynchronized = false; upsert = true; } if (upsert) { updateRequest.ToUpsert.Add(DataStoreItemDTO.FromObservable(itemDb)); actionLogModified.Add(new Tuple(itemDbDTO, DataStoreItemDTO.FromObservable(itemDb))); } } } if (MachineProvider.IsPPCAvailable && machineGuid == MachineProvider.Machine.Guid) { //Direct Sync Here. //Make all items "IsSynchronized = true" if success. var response = MachineProvider.MachineOperator.SendGenericRequest(updateRequest, new Transport.TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(30) }).Result; allItems.ForEach(x => x.IsSynchronized = true); } db.SaveChanges(); try { //Save action logs.. actionLogDeleted = actionLogDeleted.DistinctBy(x => x.Guid).ToList(); actionLogInserted = actionLogInserted.DistinctBy(x => x.Guid).ToList(); actionLogModified = actionLogModified.DistinctBy(x => x.Item1.Guid).ToList(); foreach (var item in actionLogDeleted) { ActionLogManager.InsertLog(ActionLogType.DataStoreItemDeleted, AuthenticationProvider.CurrentUser, $"{machine.SerialNumber} => {item.Key}", item, "Data store item deleted via FSE.", true); } foreach (var item in actionLogInserted) { ActionLogManager.InsertLog(ActionLogType.DataStoreItemCreated, AuthenticationProvider.CurrentUser, $"{machine.SerialNumber} => {item.Key}", item, "Data store item created via FSE.", false); } foreach (var item in actionLogModified) { ActionLogManager.InsertLog(ActionLogType.DataStoreItemModified, AuthenticationProvider.CurrentUser, $"{machine.SerialNumber} => {item.Item1.Key}", item.Item1, item.Item2, "Data store item modified via FSE."); } } catch (Exception ex) { LogManager.Log(ex, "Error saving action logs for data store updates."); } } return GetDataStoreModel(machineGuid).Result; }); } private void ValidateCollectionAndKey(String collection = null, String key = null) { if (collection != null) { if (!DataStoreHelper.ValidateCollectionOrKeyName(collection)) { throw new ArgumentException("Collection name contains invalid characters."); } } if (key != null) { if (!DataStoreHelper.ValidateCollectionOrKeyName(key)) { throw new ArgumentException("Item key contains invalid characters."); } } } } }