using Disco.Data.Repository; using Disco.Data.Repository.Monitor; using Disco.Models.Repository; using Disco.Services.Extensions; using Disco.Services.Tasks; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Reactive.Linq; namespace Disco.Services.Devices.DeviceFlags { public static class DeviceFlagService { private static Cache _cache; internal static Lazy> DeviceFlagAssignmentRepositoryEvents; static DeviceFlagService() { // Statically defined (lazy) Assignment Repository Definition DeviceFlagAssignmentRepositoryEvents = new Lazy>(() => RepositoryMonitor.StreamAfterCommit.Where(e => e.EntityType == typeof(DeviceFlagAssignment) && (e.EventType != RepositoryMonitorEventType.Modified || e.ModifiedProperties.Contains(nameof(DeviceFlagAssignment.RemovedDate))) ) ); } public static void Initialize(DiscoDataContext database) { _cache = new Cache(database); // Initialize Managed Groups (if configured) _cache.GetDeviceFlags().ForEach(uf => { DeviceFlagDevicesManagedGroup.Initialize(uf); DeviceFlagDeviceAssignedUsersManagedGroup.Initialize(uf); }); } public static List GetDeviceFlags() { return _cache.GetDeviceFlags(); } public static DeviceFlag GetDeviceFlag(int deviceFlagId) { return _cache.GetDeviceFlag(deviceFlagId); } #region Device Flag Maintenance public static DeviceFlag CreateDeviceFlag(DiscoDataContext database, DeviceFlag deviceFlag) { // Verify if (string.IsNullOrWhiteSpace(deviceFlag.Name)) throw new ArgumentException("The Device Flag Name is required", nameof(deviceFlag)); // Name Unique if (_cache.GetDeviceFlags().Any(f => f.Name == deviceFlag.Name)) throw new ArgumentException("Another Device Flag already exists with that name", nameof(deviceFlag)); // Clone to break reference var flag = new DeviceFlag() { Name = deviceFlag.Name, Description = deviceFlag.Description, Icon = deviceFlag.Icon, IconColour = deviceFlag.IconColour, DevicesLinkedGroup = deviceFlag.DevicesLinkedGroup, DeviceUsersLinkedGroup = deviceFlag.DeviceUsersLinkedGroup, }; database.DeviceFlags.Add(flag); database.SaveChanges(); _cache.AddOrUpdate(flag); return flag; } public static DeviceFlag Update(DiscoDataContext database, DeviceFlag deviceFlag) { // Verify if (string.IsNullOrWhiteSpace(deviceFlag.Name)) throw new ArgumentException("The Device Flag Name is required", nameof(deviceFlag)); // Name Unique if (_cache.GetDeviceFlags().Any(f => f.Id != deviceFlag.Id && f.Name == deviceFlag.Name)) throw new ArgumentException("Another Device Flag already exists with that name", nameof(deviceFlag)); database.SaveChanges(); _cache.AddOrUpdate(deviceFlag); DeviceFlagDevicesManagedGroup.Initialize(deviceFlag); DeviceFlagDeviceAssignedUsersManagedGroup.Initialize(deviceFlag); return deviceFlag; } public static void DeleteDeviceFlag(DiscoDataContext database, int deviceFlagId, IScheduledTaskStatus status) { var flag = database.DeviceFlags.Find(deviceFlagId); // Dispose of AD Managed Groups Interop.ActiveDirectory.ActiveDirectory.Context.ManagedGroups.Remove(DeviceFlagDeviceAssignedUsersManagedGroup.GetKey(flag)); Interop.ActiveDirectory.ActiveDirectory.Context.ManagedGroups.Remove(DeviceFlagDevicesManagedGroup.GetKey(flag)); // Delete Assignments status.UpdateStatus(0, $"Removing '{flag.Name}' [{flag.Id}] Device Flag", "Starting"); var flagAssignments = database.DeviceFlagAssignments.Where(fa => fa.DeviceFlagId == flag.Id).ToList(); if (flagAssignments.Count > 0) { status.UpdateStatus(20, "Removing flag from devices"); flagAssignments.ForEach(flagAssignment => database.DeviceFlagAssignments.Remove(flagAssignment)); database.SaveChanges(); } // Delete Flag status.UpdateStatus(90, "Deleting Device Flag"); database.DeviceFlags.Remove(flag); database.SaveChanges(); // Remove from Cache _cache.Remove(deviceFlagId); status.Finished($"Successfully Deleted Device Flag: '{flag.Name}' [{flag.Id}]"); } #endregion #region Bulk Assignment public static IEnumerable BulkAssignAddDevices(DiscoDataContext database, DeviceFlag deviceFlag, User technician, string comments, List devices, IScheduledTaskStatus status) { if (devices.Count > 0) { double progressInterval; const int databaseChunkSize = 100; comments = string.IsNullOrWhiteSpace(comments) ? null : comments.Trim(); var addDevices = devices.Where(d => !d.DeviceFlagAssignments.Any(a => a.DeviceFlagId == deviceFlag.Id && !a.RemovedDate.HasValue)).ToList(); progressInterval = (double)100 / addDevices.Count; var addedDeviceAssignments = addDevices.Chunk(databaseChunkSize).SelectMany((chunk, chunkIndex) => { var chunkIndexOffset = databaseChunkSize * chunkIndex; var chunkResults = chunk.Select((device, index) => { status.UpdateStatus((chunkIndexOffset + index) * progressInterval, $"Assigning Flag: {device}"); return device.OnAddDeviceFlag(database, deviceFlag, technician, comments); }).ToList(); // Save Chunk Items to Database database.SaveChanges(); return chunkResults; }).Where(fa => fa != null).ToList(); status.SetFinishedMessage($"{addDevices.Count} Devices/s Added; {(devices.Count - addDevices.Count)} Devices/s Skipped"); return addedDeviceAssignments; } else { status.SetFinishedMessage("No changes found"); return Enumerable.Empty(); } } public static IEnumerable BulkAssignOverrideDevices(DiscoDataContext database, DeviceFlag deviceFlag, User technician, string comments, List devices, IScheduledTaskStatus status) { double progressInterval; const int databaseChunkSize = 100; comments = string.IsNullOrWhiteSpace(comments) ? null : comments.Trim(); status.UpdateStatus(0, "Calculating assignment changes"); var currentAssignments = database.DeviceFlagAssignments.Include(fa => fa.Device).Where(a => a.DeviceFlagId == deviceFlag.Id && !a.RemovedDate.HasValue).ToList(); var removeAssignments = currentAssignments.Where(ca => !devices.Any(d => d.SerialNumber.Equals(ca.DeviceSerialNumber, StringComparison.OrdinalIgnoreCase))).ToList(); var addAssignments = devices.Where(d => !currentAssignments.Any(ca => ca.DeviceSerialNumber.Equals(d.SerialNumber, StringComparison.OrdinalIgnoreCase))).ToList(); if (removeAssignments.Count > 0 || addAssignments.Count > 0) { progressInterval = (double)100 / (removeAssignments.Count + addAssignments.Count); var removedDateTime = DateTime.Now; // Remove Assignments removeAssignments.Chunk(databaseChunkSize).SelectMany((chunk, chunkIndex) => { var chunkIndexOffset = (chunkIndex * databaseChunkSize) + removeAssignments.Count; var chunkResults = chunk.Select((flagAssignment, index) => { status.UpdateStatus((chunkIndexOffset + index) * progressInterval, $"Removing Flag: {flagAssignment.Device}"); flagAssignment.OnRemoveUnsafe(database, technician); return flagAssignment; }).ToList(); // Save Chunk Items to Database database.SaveChanges(); return chunkResults; }).ToList(); // Add Assignments var addedAssignments = addAssignments.Chunk(databaseChunkSize).SelectMany((chunk, chunkIndex) => { var chunkIndexOffset = (chunkIndex * databaseChunkSize) + removeAssignments.Count; var chunkResults = chunk.Select((device, index) => { status.UpdateStatus((chunkIndexOffset + index) * progressInterval, $"Assigning Flag: {device}"); return device.OnAddDeviceFlag(database, deviceFlag, technician, comments); }).ToList(); // Save Chunk Items to Database database.SaveChanges(); return chunkResults; }).ToList(); status.SetFinishedMessage($"{addAssignments.Count} Devices/s Added; {removeAssignments.Count} Devices/s Removed; {(devices.Count - addAssignments.Count)} Devices/s Skipped"); return addedAssignments; } else { status.SetFinishedMessage("No changes found"); return Enumerable.Empty(); } } #endregion public static string RandomUnusedIcon() { return UIHelpers.RandomIcon(_cache.GetDeviceFlags().Select(f => f.Icon)); } public static string RandomUnusedThemeColour() { return UIHelpers.RandomThemeColour(_cache.GetDeviceFlags().Select(f => f.IconColour)); } } }