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();
}
}
}
}