using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.BL; using Tango.BL.Builders; using Tango.BL.DTO; using Tango.BL.Entities; using Tango.Core.Threading; using Tango.FSE.BL.CacheEntities; using Z.EntityFramework.Plus; using Z.EntityFramework.Extensions; using Tango.BL.Enumerations; using Tango.Settings; using Tango.FSE.Web.Messages; namespace Tango.FSE.BL.Services { /// /// Represents the machines CRUD API for retrieving and updating machines. /// /// public class MachinesService : FSEServiceBase { private const string MACHINES_COLLECTION = "Machines"; private const string FULL_MACHINES_COLLECTION = "Machines_Full"; private bool _fullMachinesCachePerformed; private MemoryCacheDictionary _machinesCache; private MemoryCacheDictionary _machinesCacheFull; /// /// Initializes a new instance of the class. /// public MachinesService() { _machinesCache = MemoryCache.GetOrCreateCache(MACHINES_COLLECTION); _machinesCacheFull = MemoryCache.GetOrCreateCache(FULL_MACHINES_COLLECTION); } /// /// Gets a machine by the specified serial number. /// A machine that exists outside of the current user organization will not be retrieved. /// The machine will be retrieved along with it's organization only. /// Once a machine retrieved successfully, it will be cached on disk and on memory. /// The resolver behavior is In-Memory first, then Online, then Disk. /// /// The serial number. /// public Task GetMachine(String serialNumber) { bool allowAll = CurrentUser.HasPermission(Permissions.FSE_ConnectAnyMachine); return DataResolver.Builder.New() .ConfigureCascade(DataResolverNode.InMemoryCache, DataResolverNode.Web, DataResolverNode.Online, DataResolverNode.DiskCache) .InMemoryCache((context) => { return _machinesCache.Get(serialNumber).ToObservable(); }) .Web((context) => { var response = WebClient.GetMachine(new GetMachineRequest() { AllowAllMachines = allowAll, OrganizationGuid = CurrentUser.OrganizationGuid, SerialNumber = serialNumber }).GetAwaiter().GetResult(); if (response.Machine != null) { var machine = response.Machine.ToObservable(); LogManager.Log("Machine retrieved successfully. Caching machine..."); var cachedMachine = CachedMachine.FromObservable(machine); //Store in memory cache. _machinesCache.Put(serialNumber, cachedMachine); //Store disk cache. try { using (var cache = DiskCache.CreateContext()) { cache.Database.GetCollection(MACHINES_COLLECTION).Upsert(cachedMachine); } } catch (Exception ex) { LogManager.Log(ex, "Error caching machine on disk."); } return machine; } throw new ArgumentOutOfRangeException($"Could not locate machine with serial number '{serialNumber}' using the remote server."); }) .Online((context) => { using (ObservablesContext db = ObservablesContext.CreateDefault()) { var machine = new MachineBuilder(db) .Set(x => (allowAll || x.OrganizationGuid == CurrentUser.OrganizationGuid) && x.SerialNumber == serialNumber) .WithOrganization() .Build(); if (machine != null) { LogManager.Log("Machine retrieved successfully. Caching machine..."); var cachedMachine = CachedMachine.FromObservable(machine); //Store in memory cache. _machinesCache.Put(serialNumber, cachedMachine); //Store disk cache. try { using (var cache = DiskCache.CreateContext()) { cache.Database.GetCollection(MACHINES_COLLECTION).Upsert(cachedMachine); } } catch (Exception ex) { LogManager.Log(ex, "Error caching machine on disk."); } return machine; } throw new ArgumentOutOfRangeException($"Could not locate machine with serial number '{serialNumber}' on the remote database."); } }) .DiskCache((context) => { using (var cache = DiskCache.CreateContext()) { var cachedMachine = cache.Database.GetCollection(MACHINES_COLLECTION).FindOne(x => x.SerialNumber == serialNumber && (allowAll || x.OrganizationGuid == CurrentUser.OrganizationGuid)); //Store in memory cache. _machinesCache.Put(serialNumber, cachedMachine); return cachedMachine.ToObservable(); } }) .BuildExecuteAsync(); } /// /// Gets a machine by the specified serial number along with all it's associated entities like /// Organization, configuration, hardware version, machine version, spools calibration and color calibration. /// Once a machine is retrieved it will be cached on disk and in memory. /// If the specified machine does not exists within the current user organization it will not be retrieved. /// The resolver behavior is In-Memory first, then Online, then Disk. /// On the first call to this method, a process of fully caching all the current user organization machines starts in the background. /// /// The serial number. /// public Task GetMachineFull(String serialNumber) { return GetMachineFull(serialNumber, true); } private Task GetMachineFull(String serialNumber, bool enableLogs) { bool allowAll = CurrentUser.HasPermission(Permissions.FSE_ConnectAnyMachine); return DataResolver.Builder.New() .EnableLogs(enableLogs) .ConfigureCascade(DataResolverNode.InMemoryCache, DataResolverNode.Web, DataResolverNode.Online, DataResolverNode.DiskCache) .InMemoryCache((context) => { return _machinesCacheFull.Get(serialNumber).ToObservable(); }) .Web((context) => { var response = WebClient.GetMachine(new GetMachineRequest() { AllowAllMachines = allowAll, OrganizationGuid = CurrentUser.OrganizationGuid, SerialNumber = serialNumber, GetExtendedInfo = true }).GetAwaiter().GetResult(); if (response.Machine != null) { var machine = response.Machine.ToObservable(); if (enableLogs) LogManager.Log("Machine retrieved successfully. Caching machine on disk and in memory..."); var cachedMachine = CachedMachine.FromObservable(machine); //Store in memory cache. _machinesCacheFull.Put(serialNumber, cachedMachine); //Store disk cache. try { using (var cache = DiskCache.CreateContext()) { cache.Database.GetCollection(FULL_MACHINES_COLLECTION).Upsert(cachedMachine); } } catch (Exception ex) { LogManager.Log(ex, $"Error caching machine '{serialNumber}' on disk."); } return machine; } else { throw new ArgumentOutOfRangeException($"Could not locate machine with serial number '{serialNumber}' on the remote database."); } }) .Online((context) => { using (ObservablesContext db = ObservablesContext.CreateDefault()) { var machine = new MachineBuilder(db) .Set(x => (allowAll || x.OrganizationGuid == CurrentUser.OrganizationGuid) && x.SerialNumber == serialNumber) .WithOrganization() .WithVersion() .WithSpools() .WithCats() .WithConfiguration().Build(); if (machine != null) { if (enableLogs) LogManager.Log("Machine retrieved successfully. Caching machine on disk and in memory..."); var cachedMachine = CachedMachine.FromObservable(machine); //Store in memory cache. _machinesCacheFull.Put(serialNumber, cachedMachine); //Store disk cache. try { using (var cache = DiskCache.CreateContext()) { cache.Database.GetCollection(FULL_MACHINES_COLLECTION).Upsert(cachedMachine); } } catch (Exception ex) { LogManager.Log(ex, $"Error caching machine '{serialNumber}' on disk."); } if (SettingsManager.Default.GetOrCreate().PerformFullOrganizationMachinesCaching) { if (!_fullMachinesCachePerformed) { _fullMachinesCachePerformed = true; //Continue loading the rest of the machines on a separate thread... ThreadFactory.StartNew(() => { LogManager.Log("Starting caching entire organization machines on disk and in memory..."); var serials = new List(); using (ObservablesContext d = ObservablesContext.CreateDefault()) { serials = d.Machines.Where(x => x.OrganizationGuid == CurrentUser.OrganizationGuid).Select(x => x.SerialNumber).ToList(); } foreach (var serial in serials) { try { var fm = GetMachineFull(serial, false).Result; } catch (Exception ex) { LogManager.Log(ex, $"Error occurred while trying to cache machine '{serial}' in the background. Skipping..."); } } LogManager.Log("Full organization machines cache completed."); }); } } return machine; } else { throw new ArgumentOutOfRangeException($"Could not locate machine with serial number '{serialNumber}' on the remote database."); } } }) .DiskCache((context) => { using (var cache = DiskCache.CreateContext()) { var cachedMachine = cache.Database.GetCollection(FULL_MACHINES_COLLECTION).FindOne(x => x.SerialNumber == serialNumber && x.OrganizationGuid == CurrentUser.OrganizationGuid); if (cachedMachine == null) { throw new KeyNotFoundException("The machine entity could not be found on the remote server or local cache.\nPlease check your Internet connection and try again."); } //Store in memory cache. _machinesCacheFull.Put(serialNumber, cachedMachine); return cachedMachine.ToObservable(); } }) .BuildExecuteAsync(); } /// /// Gets the current user organization's machines. /// Once this method is called, all machines will be cached. /// The resolver behavior is In-Memory first, then Online, then Disk. /// /// public Task> GetAllMachines() { bool allowAll = CurrentUser.HasPermission(Permissions.FSE_ConnectAnyMachine); return DataResolver>.Builder.New() .ConfigureCascade(DataResolverNode.InMemoryCache, DataResolverNode.Web, DataResolverNode.Online, DataResolverNode.DiskCache) .InMemoryCache((context) => { var machines = _machinesCache .ToList() .Where(x => allowAll || x.OrganizationGuid == CurrentUser.OrganizationGuid) .Select(x => x.ToObservable()) .ToList(); if (machines.Count == 0) { throw new IndexOutOfRangeException("The memory cache did contain any machines."); } return machines; }) .Web((context) => { var response = WebClient.GetAllMachines(new GetAllMachinesRequest() { AllowAllMachines = allowAll, OrganizationGuid = CurrentUser.OrganizationGuid, }).GetAwaiter().GetResult(); var machines = response.Machines.Select(x => x.ToObservable()).ToList(); List cachedMachines = new List(); foreach (var machine in machines) { try { var cachedMachine = CachedMachine.FromObservable(machine); cachedMachines.Add(cachedMachine); } catch (Exception ex) { LogManager.Log(ex, $"Error creating cached machine (partial) '{machine.SerialNumber}'."); } } try { foreach (var cachedMachine in cachedMachines) { _machinesCache.Put(cachedMachine.SerialNumber, cachedMachine); } } catch (Exception ex) { LogManager.Log(ex, "Error caching machines (partial) to memory."); } try { using (var cache = DiskCache.CreateContext()) { var collection = cache.Database.GetCollection(MACHINES_COLLECTION); foreach (var cachedMachine in cachedMachines) { try { collection.Upsert(cachedMachine); } catch (Exception ex) { LogManager.Log(ex, $"Error caching machine '{cachedMachine.SerialNumber}' (partial) to disk."); } } } } catch (Exception ex) { LogManager.Log(ex, "Error caching machines (partial) to disk."); } return machines; }) .Online((context) => { using (ObservablesContext db = ObservablesContext.CreateDefault()) { var machines = db.Machines.Where(x => allowAll || x.OrganizationGuid == CurrentUser.OrganizationGuid).Include(x => x.Organization).ToList(); List cachedMachines = new List(); foreach (var machine in machines) { try { var cachedMachine = CachedMachine.FromObservable(machine); cachedMachines.Add(cachedMachine); } catch (Exception ex) { LogManager.Log(ex, $"Error creating cached machine (partial) '{machine.SerialNumber}'."); } } try { foreach (var cachedMachine in cachedMachines) { _machinesCache.Put(cachedMachine.SerialNumber, cachedMachine); } } catch (Exception ex) { LogManager.Log(ex, "Error caching machines (partial) to memory."); } try { using (var cache = DiskCache.CreateContext()) { var collection = cache.Database.GetCollection(MACHINES_COLLECTION); foreach (var cachedMachine in cachedMachines) { try { collection.Upsert(cachedMachine); } catch (Exception ex) { LogManager.Log(ex, $"Error caching machine '{cachedMachine.SerialNumber}' (partial) to disk."); } } } } catch (Exception ex) { LogManager.Log(ex, "Error caching machines (partial) to disk."); } return machines; } }) .DiskCache((context) => { using (var cache = DiskCache.CreateContext()) { var collection = cache.Database.GetCollection(MACHINES_COLLECTION); var cachedMachines = collection .Find(x => allowAll || x.OrganizationGuid == CurrentUser.OrganizationGuid) .ToList(); foreach (var cachedMachine in cachedMachines) { _machinesCache.Put(cachedMachine.SerialNumber, cachedMachine); } return cachedMachines.Select(x => x.ToObservable()).ToList(); } }) .BuildExecuteAsync(); } /// /// Updates the specified machine. /// /// The machine. /// /// public async Task UpdateMachine(Machine machine) { throw new NotImplementedException(); if (ConnectivityProvider.IsOnline) { using (var db = ObservablesContext.CreateDefault()) { await db.SingleUpdateAsync(machine); //Update using Z EF Extensions (no problem with out of context entity!)... return machine; } } else { throw new InternetConnectionException(); } } } }