aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Themes
ModeNameSize
-rw-r--r--DarkThemeColors.xaml17377logstatsplain
-rw-r--r--Generic.xaml6641logstatsplain
-rw-r--r--LightThemeColors.xaml14161logstatsplain
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Authentication;
using System.Threading.Tasks;
using System.Web.Http;
using Tango.BL;
using Tango.BL.Builders;
using Tango.BL.Entities;
using Tango.BL.Enumerations;
using Tango.Core;
using Tango.Core.DB;
using Tango.Logging;
using Tango.MachineService.Models;
using Tango.PPC.Common.Web;
using Tango.Web.Controllers;
using Tango.Web.Helpers;
using Tango.Web.SMO;
using Tango.Web.Storage;
using System.Data.Entity;
using Tango.Web.Security;
using Tango.Web.ActiveDirectory;
using Tango.Core.Cryptography;
using Tango.MachineService.Filters;
using Tango.BL.DTO;
using Z.EntityFramework.Plus;
using Twilio;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;

namespace Tango.MachineService.Controllers
{
    public class PPCController : TangoController<PPCController.TokenObject>
    {
        private static List<PPCPendingUpload> _pendingUploads;
        private static List<PPCPendingUpdate> _pendingUpdates;
        private ActiveDirectoryManager _ad_manager;
        public const int SQL_TEMP_CREDENTIALS_EXP_MINUTS = 30;

        public class TokenObject
        {
            public LoginMode Mode { get; set; }
            public String UserGuid { get; set; }
            public String MachineGuid { get; set; }
        }

        #region Constructors

        static PPCController()
        {
            _pendingUploads = new List<PPCPendingUpload>();
            _pendingUpdates = new List<PPCPendingUpdate>();
            TwilioClient.Init(MachineServiceConfig.TWILIO_ACCOUNT_SID, MachineServiceConfig.TWILIO_AUTH_TOKEN);
        }

        public PPCController()
        {
            _ad_manager = new ActiveDirectoryManager();
        }

        #endregion

        #region Setup & Update

        [HttpPost]
        [JwtTokenFilter]
        public MachineSetupResponse MachineSetup(MachineSetupRequest request)
        {
            MachineSetupResponse response = new MachineSetupResponse();
            response.NotifyCompletedToken = Guid.NewGuid().ToString();

            LogManager.Log("Setup request received: " + request.ToString());

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                String machine_guid = RequestToken.Object.MachineGuid;

                var machine = db.Machines.Include(x => x.Organization).SingleOrDefault(x => x.Guid == machine_guid);

                if (machine == null)
                {
                    throw new AuthenticationException("The specified serial number could not be found.");
                }

                if (machine.SetupActivation && String.IsNullOrWhiteSpace(machine.OsKey))
                {
                    throw new InvalidDataException("The specified machine is configured to perform an OS activation but is not associated with an OS activation key.");
                }

                if (String.IsNullOrWhiteSpace(request.DeviceID))
                {
                    throw new InvalidDataException("Device id not set.");
                }

                if (String.IsNullOrWhiteSpace(request.DeviceName))
                {
                    throw new InvalidDataException("Device name not set.");
                }

                if (machine.IsDeviceRegistered && machine.DeviceId != request.DeviceID)
                {
                    throw new InvalidDataException("The specified machine is already registered and device id is invalid.");
                }

                machine.DeviceName = request.DeviceName;
                machine.DeviceId = request.DeviceID;
                machine.IsDeviceRegistered = true;

                var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == machine.MachineVersionGuid);

                var latest_machine_version = db.TangoVersions.Where(x => x.MachineVersionGuid == machine_version.Guid && x.Tag == machine.VersionTag && !x.Disabled).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault();

                response.Version = latest_machine_version.Version;
                response.FirmwareVersion = latest_machine_version.FirmwareVersion;
                response.Tag = latest_machine_version.Tag;

                var manager = new BlobStorageManager();
                var container = manager.GetContainer(MachineServiceConfig.TANGO_VERSIONS_CONTAINER);
                var blob = container.GetBlockBlobReference(latest_machine_version.BlobName);

                response.BlobAddress = blob.GenerateReadSignature(TimeSpan.FromMinutes(60));

                if (!String.IsNullOrWhiteSpace(MachineServiceConfig.CDN_ENDPOINT))
                {
                    response.CdnAddress = MachineServiceConfig.CDN_ENDPOINT + blob.Uri.AbsolutePath;
                }

                DbCredentials credentials = new DbCredentials();

                using (SmoManager smo = new SmoManager())
                {
                    credentials = smo.CreateRandomLoginAndUser();

                    Task.Delay(TimeSpan.FromMinutes(SQL_TEMP_CREDENTIALS_EXP_MINUTS)).ContinueWith((x) =>
                    {
                        using (SmoManager m = new SmoManager())
                        {
                            m.DeleteLoginAndUser(credentials.UserName);
                        }
                    });
                }

                response.DataSource = new DataSource()
                {
                    Address = MachineServiceConfig.DB_ADDRESS,
                    Catalog = MachineServiceConfig.DB_CATALOG,
                    UserName = credentials.UserName,
                    Password = credentials.Password,
                    IntegratedSecurity = false,
                    Type = DataSourceType.SQLServer,
                };

                response.OSKey = machine.OsKey;
                response.SetupActivation = machine.SetupActivation;
                response.SetupRemoteAssistance = machine.SetupRemoteAssistance;
                response.SetupUWF = machine.SetupUwf;
                response.SetupFirmware = machine.SetupFirmware;
                response.SetupFPGA = machine.SetupFpga;
                response.IsDemo = machine.IsDemo;
                response.Organization = machine.Organization.Name;

                TangoUpdate tangoUpdate = new TangoUpdate();
                tangoUpdate.ApplicationVersion = latest_machine_version.Version;
                tangoUpdate.FirmwareVersion = latest_machine_version.FirmwareVersion;
                tangoUpdate.ApplicationVersionTag = latest_machine_version.Tag;
                tangoUpdate.MachineGuid = machine.Guid;
                tangoUpdate.StartDate = DateTime.UtcNow;
                tangoUpdate.UpdateStatus = TangoUpdateStatuses.SetupStarted;
                db.TangoUpdates.Add(tangoUpdate);

                machine.ProductionDate = DateTime.UtcNow;

                db.SaveChanges();

                _pendingUpdates.Add(new PPCPendingUpdate()
                {
                    Token = response.NotifyCompletedToken,
                    TangoUpdateGuid = tangoUpdate.Guid,
                });

                Task.Factory.StartNew(() =>
                {
                    using (ObservablesContext b = ObservablesWebContext.CreateContext())
                    {
                        //Reset Job Runs.
                        try
                        {
                            b.JobRuns.Where(x => x.MachineGuid == machine.Guid && x.IsSynchronized).Update(x => new JobRun() { IsSynchronized = false });
                        }
                        catch (Exception ex)
                        {
                            LogManager.Log(ex, $"Error resetting synchronized job runs for machine '{machine.SerialNumber}'.");
                        }

                        try
                        {
                            b.DataStoreItems.Where(x => x.MachineGuid == machine.Guid).Update(x => new DataStoreItem() { IsSynchronized = false });
                        }
                        catch (Exception ex)
                        {
                            LogManager.Log(ex, $"Error resetting synchronized data store items for machine '{machine.SerialNumber}'.");
                        }

                        //Reset Events.
                        //b.MachinesEvents.Where(x => x.MachineGuid == machine.Guid).Update(x => new MachinesEvent() { IsSynchronized = false });
                        //Reset Jobs.
                        //b.Jobs.Where(x => x.MachineGuid == machine.Guid).Update(x => new Job() { IsSynchronized = false });
                    }
                });
            }

            return response;
        }

        [HttpPost]
        [JwtTokenFilter]
        public DownloadUpdateResponse MachineUpdate(DownloadUpdateRequest request)
        {
            DownloadUpdateResponse response = new DownloadUpdateResponse();
            response.NotifyCompletedToken = Guid.NewGuid().ToString();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                db.Configuration.LazyLoadingEnabled = false;
                String machine_guid = RequestToken.Object.MachineGuid;

                var machine = db.Machines.SingleOrDefault(x => x.Guid == machine_guid);

                if (machine == null)
                {
                    throw new AuthenticationException("The specified serial number could not be found.");
                }

                var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == machine.MachineVersionGuid);

                var latest_machine_version = db.TangoVersions.Where(x => x.MachineVersionGuid == machine_version.Guid && x.Tag == machine.VersionTag && !x.Disabled).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault();

                response.Version = latest_machine_version.Version;
                response.FirmwareVersion = latest_machine_version.FirmwareVersion;
                response.Tag = latest_machine_version.Tag;

                var manager = new BlobStorageManager();
                var container = manager.GetContainer(MachineServiceConfig.TANGO_VERSIONS_CONTAINER);
                var blob = container.GetBlockBlobReference(latest_machine_version.BlobName);

                response.BlobAddress = blob.GenerateReadSignature(TimeSpan.FromMinutes(60));

                if (!String.IsNullOrWhiteSpace(MachineServiceConfig.CDN_ENDPOINT))
                {
                    response.CdnAddress = MachineServiceConfig.CDN_ENDPOINT + blob.Uri.AbsolutePath;
                }

                DbCredentials credentials = new DbCredentials();

                using (SmoManager smo = new SmoManager())
                {
                    credentials = smo.CreateRandomLoginAndUser();

                    Task.Delay(TimeSpan.FromMinutes(SQL_TEMP_CREDENTIALS_EXP_MINUTS)).ContinueWith((x) =>
                    {
                        using (SmoManager m = new SmoManager())
                        {
                            m.DeleteLoginAndUser(credentials.UserName);
                        }
                    });
                }

                response.DataSource = new DataSource()
                {
                    Address = MachineServiceConfig.DB_ADDRESS,
                    Catalog = MachineServiceConfig.DB_CATALOG,
                    UserName = credentials.UserName,
                    Password = credentials.Password,
                    IntegratedSecurity = false,
                    Type = DataSourceType.SQLServer,
                };

                TangoUpdate tangoUpdate = new TangoUpdate();
                tangoUpdate.ApplicationVersion = latest_machine_version.Version;
                tangoUpdate.FirmwareVersion = latest_machine_version.FirmwareVersion;
                tangoUpdate.ApplicationVersionTag = latest_machine_version.Tag;
                tangoUpdate.MachineGuid = machine.Guid;
                tangoUpdate.StartDate = DateTime.UtcNow;
                tangoUpdate.UpdateStatus = TangoUpdateStatuses.UpdateStarted;
                db.TangoUpdates.Add(tangoUpdate);

                db.SaveChanges();

                _pendingUpdates.Add(new PPCPendingUpdate()
                {
                    Token = response.NotifyCompletedToken,
                    TangoUpdateGuid = tangoUpdate.Guid,
                });
            }

            return response;
        }

        [HttpPost]
        [JwtTokenFilter]
        public MachineUpdateCompletedResponse NotifyUpdateCompleted(MachineUpdateCompletedRequest request)
        {
            var pendingUpdate = _pendingUpdates.SingleOrDefault(x => x.Token == request.Token);
            if (pendingUpdate != null)
            {
                _pendingUpdates.Remove(pendingUpdate);

                using (ObservablesContext db = ObservablesWebContext.CreateContext())
                {
                    var tangoUpdate = db.TangoUpdates.SingleOrDefault(x => x.Guid == pendingUpdate.TangoUpdateGuid);

                    if (tangoUpdate != null)
                    {
                        tangoUpdate.LastUpdated = DateTime.UtcNow;
                        tangoUpdate.UpdateStatus = request.Status;
                        tangoUpdate.EndDate = DateTime.UtcNow;
                        tangoUpdate.FailedReason = request.FailedReason;
                        tangoUpdate.FailedLog = request.FailedLog;

                        if (request.ReportsAboutDbCheckNoDifferences)
                        {
                            db.TangoUpdates.Remove(tangoUpdate);
                        }

                        db.SaveChanges();
                    }
                }
            }

            return new MachineUpdateCompletedResponse();
        }

        [HttpPost]
        [JwtTokenFilter]
        public CheckForUpdateResponse CheckForUpdates(CheckForUpdateRequest request)
        {
            CheckForUpdateResponse response = new CheckForUpdateResponse();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                String machine_guid = RequestToken.Object.MachineGuid;

                var machine = db.Machines.SingleOrDefault(x => x.Guid == machine_guid);

                if (machine == null)
                {
                    throw new AuthenticationException("The specified serial number could not be found.");
                }

                response.SetupFirmware = machine.SetupFirmware;
                response.SetupFPGA = machine.SetupFpga;

                if (!machine.SuspendVersionUpdate)
                {
                    var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == machine.MachineVersionGuid);

                    var latest_machine_version = db.TangoVersions.Where(x => x.MachineVersionGuid == machine_version.Guid && x.Tag == machine.VersionTag && !x.Disabled).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault();

                    if (Version.Parse(latest_machine_version.Version) > Version.Parse(request.Version))
                    {
                        response.IsUpdateAvailable = true;
                    }

                    if (request.Tag != machine.VersionTag)
                    {
                        response.IsUpdateAvailable = true;
                    }

                    if (!response.IsUpdateAvailable && !machine.IsDemo && machine.SetupFirmware)
                    {
                        if (!String.IsNullOrWhiteSpace(request.FirmwareVersion))
                        {
                            if (Version.Parse(latest_machine_version.FirmwareVersion) > Version.Parse(request.FirmwareVersion))
                            {
                                response.IsUpdateAvailable = true;
                            }
                        }
                    }

                    response.Version = latest_machine_version.Version;
                    response.FirmwareVersion = latest_machine_version.FirmwareVersion;

                    //Compare database

                    var rmls = db.Rmls.Select(x => new { x.Guid, x.LastUpdated }).ToList();
                    var hardwareVersions = db.HardwareVersions.Select(x => new { x.Guid, x.LastUpdated }).ToList();
                    var catalogs = db.ColorCatalogs.Select(x => new { x.Guid, x.LastUpdated }).ToList();

                    var arr = request.UsedRmlsGuids.ToArray();
                    var existingRml = db.Rmls.Where(x => arr.Contains(x.Guid)).Select(x => x.Guid).Distinct().ToList();
                    response.UsedNotExistingRmlsGuids = arr.Where(x => !existingRml.Exists(y => y == x)).ToList();

                    bool hasDatabaseUpdates = false;

                    hasDatabaseUpdates = machine.LastUpdated > request.MachineLastUpdated;

                    if (!hasDatabaseUpdates)
                    {
                        hasDatabaseUpdates = rmls.Exists(x => request.Rmls.Exists(y => x.Guid == y.Guid && x.LastUpdated > y.LastUpdated) || !request.Rmls.Exists(y => x.Guid == y.Guid));
                    }

                    if (!hasDatabaseUpdates)
                    {
                        hasDatabaseUpdates = hardwareVersions.Exists(x => request.HardwareVersions.Exists(y => x.Guid == y.Guid && x.LastUpdated > y.LastUpdated) || !request.HardwareVersions.Exists(y => x.Guid == y.Guid));
                    }

                    if (!hasDatabaseUpdates)
                    {
                        hasDatabaseUpdates = catalogs.Exists(x => request.Catalogs.Exists(y => x.Guid == y.Guid && x.LastUpdated > y.LastUpdated) || !request.Catalogs.Exists(y => x.Guid == y.Guid));
                    }

                    if (hasDatabaseUpdates)
                    {
                        response.IsDatabaseUpdateAvailable = true;
                        response.UpdateDBResponse = UpdateDB(new UpdateDBRequest()
                        {
                            ApplicationVersion = request.Version,
                            FirmwareVersion = request.FirmwareVersion
                        });
                    }

                    //Compare database

                    if (machine.ForceVersionUpdate)
                    {
                        response.IsUpdateAvailable = true;
                    }
                }
            }

            return response;
        }

        [HttpPost]
        [JwtTokenFilter]
        public UpdateDBResponse UpdateDB(UpdateDBRequest request)
        {
            UpdateDBResponse response = new UpdateDBResponse();
            response.NotifyCompletedToken = Guid.NewGuid().ToString();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {

                String machine_guid = RequestToken.Object.MachineGuid;

                var machine = db.Machines.SingleOrDefault(x => x.Guid == machine_guid);

                if (machine == null)
                {
                    throw new AuthenticationException("The specified serial number could not be found.");
                }

                response.PerformSchemaUpdate = machine.PerformSchemaUpdateOnDataUpdate;

                DbCredentials credentials = new DbCredentials();

                using (SmoManager manager = new SmoManager())
                {
                    credentials = manager.CreateRandomLoginAndUser();

                    Task.Delay(TimeSpan.FromMinutes(SQL_TEMP_CREDENTIALS_EXP_MINUTS)).ContinueWith((x) =>
                    {
                        using (SmoManager m = new SmoManager())
                        {
                            m.DeleteLoginAndUser(credentials.UserName);
                        }
                    });
                }

                response.DataSource = new DataSource()
                {
                    Address = MachineServiceConfig.DB_ADDRESS,
                    Catalog = MachineServiceConfig.DB_CATALOG,
                    UserName = credentials.UserName,
                    Password = credentials.Password,
                    IntegratedSecurity = false,
                    Type = DataSourceType.SQLServer,
                };

                TangoUpdate tangoUpdate = new TangoUpdate();
                tangoUpdate.ApplicationVersion = request.ApplicationVersion;
                tangoUpdate.FirmwareVersion = request.FirmwareVersion;
                tangoUpdate.ApplicationVersionTag = machine.VersionTag;
                tangoUpdate.MachineGuid = machine.Guid;
                tangoUpdate.StartDate = DateTime.UtcNow;
                tangoUpdate.UpdateStatus = TangoUpdateStatuses.DatabaseStarted;
                db.TangoUpdates.Add(tangoUpdate);

                db.SaveChanges();

                _pendingUpdates.Add(new PPCPendingUpdate()
                {
                    Token = response.NotifyCompletedToken,
                    TangoUpdateGuid = tangoUpdate.Guid,
                });
            }

            return response;
        }

        #endregion

        #region Synchronization

        [HttpPost]
        [JwtTokenFilter]
        public UploadMachineDataResponse UploadMachineData(UploadMachineDataRequest request)
        {
            UploadMachineDataResponse response = new UploadMachineDataResponse();
            response.NotifyCompletedToken = Guid.NewGuid().ToString();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid);

                if (machine == null)
                {
                    throw new AuthenticationException("The specified machine could not be found.");
                }

                TangoUpdate tangoUpdate = new TangoUpdate();
                tangoUpdate.ApplicationVersion = request.ApplicationVersion;
                tangoUpdate.FirmwareVersion = request.FirmwareVersion;
                tangoUpdate.ApplicationVersionTag = machine.VersionTag;
                tangoUpdate.MachineGuid = machine.Guid;
                tangoUpdate.StartDate = DateTime.UtcNow;
                tangoUpdate.UpdateStatus = TangoUpdateStatuses.SynchronizationStarted;
                db.TangoUpdates.Add(tangoUpdate);
                db.SaveChanges();

                _pendingUpdates.Add(new PPCPendingUpdate()
                {
                    Token = response.NotifyCompletedToken,
                    TangoUpdateGuid = tangoUpdate.Guid,
                });
            }

            try
            {
                using (ObservablesContext db = ObservablesWebContext.CreateContext())
                {
                    var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid);

                    if (machine == null)
                    {
                        throw new AuthenticationException("The specified machine could not be found.");
                    }
                }

                //Insert/Replace Jobs.
                foreach (var dto in request.Jobs)
                {
                    using (ObservablesContext db = ObservablesWebContext.CreateContext())
                    {
                        try
                        {
                            var job = dto.ToObservable();

                            job.ID = 0;
                            job.CustomerGuid = null;
                            job.IsSynchronized = true;

                            var existingJob = db.Jobs.SingleOrDefault(x => x.Guid == job.Guid);

                            if (existingJob == null)
                            {
                                db.Jobs.Add(job);
                                db.SaveChanges();
                            }
                            else if (job.LastUpdated > existingJob.LastUpdated)
                            {
                                job.UserGuid = existingJob.UserGuid;
                                existingJob.Delete(db);
                                db.Jobs.Add(job);
                                db.SaveChanges();
                            }
                        }
                        catch (Exception ex)
                        {
                            response.FailedJobs.Add(new SynchronizationFailedEntity()
                            {
                                Guid = dto.Guid,
                                Reason = ex.FlattenMessage(),
                            });
                        }
                    }
                }

                //Insert Update DataStore Items
                foreach (var dto in request.DataStoreItems)
                {
                    using (ObservablesContext db = ObservablesWebContext.CreateContext())
                    {
                        try
                        {
                            var dataStoreItem = dto.ToObservable();
                            dataStoreItem.MachineGuid = RequestToken.Object.MachineGuid;

                            dataStoreItem.ID = 0;
                            dataStoreItem.IsSynchronized = true;

                            var existingItem = db.DataStoreItems.SingleOrDefault(x => x.Guid == dataStoreItem.Guid);

                            if (existingItem == null)
                            {
                                db.DataStoreItems.Add(dataStoreItem);
                                db.SaveChanges();
                            }
                            else if (dataStoreItem.LastUpdated >= existingItem.LastUpdated)
                            {
                                existingItem.DataType = dataStoreItem.DataType;
                                existingItem.Value = dataStoreItem.Value;
                                existingItem.IsSynchronized = true;
                                existingItem.LastUpdated = dataStoreItem.LastUpdated;
                                db.SaveChanges();
                            }
                        }
                        catch (Exception ex)
                        {
                            response.FailedDataStoreItems.Add(new SynchronizationFailedEntity()
                            {
                                Guid = dto.Guid,
                                Reason = ex.FlattenMessage(),
                            });
                        }
                    }
                }

                //Insert JobRuns.
                foreach (var dto in request.JobRuns)
                {
                    using (ObservablesContext db = ObservablesWebContext.CreateContext())
                    {
                        try
                        {
                            var run = dto.ToObservable();
                            run.ID = 0;
                            run.IsSynchronized = true;

                            if (db.JobRuns.SingleOrDefault(x => x.Guid == run.Guid) == null)
                            {
                                db.JobRuns.Add(run);
                                db.SaveChanges();
                            }
                        }
                        catch (Exception ex)
                        {
                            response.FailedJobRuns.Add(new SynchronizationFailedEntity()
                            {
                                Guid = dto.Guid,
                                Reason = ex.FlattenMessage(),
                            });
                        }
                    }
                }

                //Insert TangoUpdates.
                foreach (var dto in request.OfflineUpdates)
                {
                    using (ObservablesContext db = ObservablesWebContext.CreateContext())
                    {
                        try
                        {
                            var update = dto.ToObservable();
                            update.ID = 0;
                            update.IsSynchronized = true;

                            if (db.TangoUpdates.SingleOrDefault(x => x.Guid == update.Guid) == null)
                            {
                                db.TangoUpdates.Add(update);
                                db.SaveChanges();
                            }
                        }
                        catch (Exception ex)
                        {
                            response.FailedOfflineUpdates.Add(new SynchronizationFailedEntity()
                            {
                                Guid = dto.Guid,
                                Reason = ex.FlattenMessage(),
                            });
                        }
                    }
                }

                //Insert MachineEvents.
                foreach (var dto in request.MachineEvents)
                {
                    using (ObservablesContext db = ObservablesWebContext.CreateContext())
                    {
                        try
                        {
                            var ev = dto.ToObservable();
                            ev.ID = 0;
                            ev.IsSynchronized = true;

                            if (db.MachinesEvents.SingleOrDefault(x => x.Guid == ev.Guid) == null)
                            {
                                db.MachinesEvents.Add(ev);
                                db.SaveChanges();
                            }
                        }
                        catch (Exception ex)
                        {
                            response.FailedMachineEvents.Add(new SynchronizationFailedEntity()
                            {
                                Guid = dto.Guid,
                                Reason = ex.FlattenMessage(),
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                NotifyUpdateCompleted(new MachineUpdateCompletedRequest()
                {
                    Status = TangoUpdateStatuses.SynchronizationFailed,
                    FailedReason = ex.FlattenMessage(),
                    FailedLog = null,
                    Token = response.NotifyCompletedToken,
                });
                throw ex;
            }

            return response;
        }

        [HttpPost]
        [JwtTokenFilter]
        public DownloadMachineDataResponse DownloadMachineData(DownloadMachineDataRequest request)
        {
            DownloadMachineDataResponse response = new DownloadMachineDataResponse();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid);

                if (machine == null)
                {
                    throw new AuthenticationException("The specified machine could not be found.");
                }

                //Send Jobs
                if (request.RequestJobs)
                {
                    var jobs = new JobsCollectionBuilder(db).Set(x => x.MachineGuid == machine.Guid && !x.IsSynchronized).WithSegments().WithBrushStops().Query(x => x.Take(request.MaxJobs).OrderByDescending(z => z.LastUpdated)).BuildList();

                    foreach (var job in jobs)
                    {
                        JobDTO dto = JobDTO.FromObservable(job);
                        response.Jobs.Add(dto);
                    }
                }

                //Send Job Runs
                if (request.RequestJobRuns)
                {
                    var jobRuns = db.JobRuns.Where(x => x.MachineGuid == machine.Guid && !x.IsSynchronized).Take(request.MaxJobRuns).OrderByDescending(x => x.LastUpdated).ToList();

                    foreach (var jobRun in jobRuns)
                    {
                        JobRunDTO dto = JobRunDTO.FromObservable(jobRun);
                        response.JobRuns.Add(dto);
                    }
                }

                //Send Machine Events
                if (request.RequestMachineEvents)
                {
                    var machineEvents = db.MachinesEvents.Where(x => x.MachineGuid == machine.Guid && !x.IsSynchronized).Take(request.MaxMachinesEvents).OrderByDescending(x => x.LastUpdated).ToList();

                    foreach (var machineEvent in machineEvents)
                    {
                        MachinesEventDTO dto = MachinesEventDTO.FromObservable(machineEvent);
                        response.MachineEvents.Add(dto);
                    }
                }

                //Send DataStore Items
                if (request.RequestDataStoreItems)
                {
                    var dataStoreItems = db.DataStoreItems.Where(x => x.MachineGuid == machine.Guid && !x.IsSynchronized & !x.IsDeleted).Take(request.MaxDataStoreItems).OrderByDescending(x => x.LastUpdated).ToList();

                    foreach (var item in dataStoreItems)
                    {
                        DataStoreItemDTO dto = DataStoreItemDTO.FromObservable(item);
                        response.DataStoreItems.Add(dto);
                    }
                }
            }

            return response;
        }

        [HttpPost]
        [JwtTokenFilter]
        public NotifyMachineDataDownloadCompletedResponse NotifyMachineDataDownloadCompleted(NotifyMachineDataDownloadCompletedRequest request)
        {
            var response = new NotifyMachineDataDownloadCompletedResponse();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid);

                if (machine == null)
                {
                    throw new AuthenticationException("The specified machine could not be found.");
                }

                if (request.SynchronizedJobs.Count > 0)
                {
                    db.Database.ExecuteSqlCommand($"UPDATE JOBS SET IS_SYNCHRONIZED = 1 WHERE GUID IN ({String.Join(",", request.SynchronizedJobs.Select(x => "'" + x + "'"))});");
                }

                if (request.SynchronizedJobRuns.Count > 0)
                {
                    db.Database.ExecuteSqlCommand($"UPDATE JOB_RUNS SET IS_SYNCHRONIZED = 1 WHERE GUID IN ({String.Join(",", request.SynchronizedJobRuns.Select(x => "'" + x + "'"))});");
                }

                if (request.SynchronizedMachineEvents.Count > 0)
                {
                    db.Database.ExecuteSqlCommand($"UPDATE MACHINES_EVENTS SET IS_SYNCHRONIZED = 1 WHERE GUID IN ({String.Join(",", request.SynchronizedMachineEvents.Select(x => "'" + x + "'"))});");
                }

                if (request.SynchronizedDataStoreItems.Count > 0)
                {
                    db.Database.ExecuteSqlCommand($"UPDATE DATA_STORE_ITEMS SET IS_SYNCHRONIZED = 1 WHERE GUID IN ({String.Join(",", request.SynchronizedDataStoreItems.Select(x => "'" + x + "'"))});");
                }
            }

            return response;
        }

        #endregion

        #region SMS

        [HttpPost]
        [JwtTokenFilter]
        public SendSMSResponse SendSMS(SendSMSRequest request)
        {
            var response = new SendSMSResponse();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid);

                if (machine == null)
                {
                    throw new AuthenticationException("The specified machine could not be found.");
                }

                if (!machine.AllowSmsNotifications)
                {
                    throw new Exception("This machine is not allowed to send text messages. Please contact your administrator.");
                }
            }

            if (MachineServiceConfig.TWILIO_ENABLE_SMS)
            {
                if (MachineServiceConfig.TWILIO_ENABLE_ALPHA_SENDER)
                {
                    foreach (var phone in request.PhoneNumbers)
                    {
                        MessageResource.Create(messagingServiceSid: MachineServiceConfig.TWILIO_MESSAGING_SERVICE_SID, to: new PhoneNumber(phone), body: request.Message);
                    }
                }
                else
                {
                    foreach (var phone in request.PhoneNumbers)
                    {
                        MessageResource.Create(from: MachineServiceConfig.TWILIO_FROM_NUMBER, to: new PhoneNumber(phone), body: request.Message);
                    }
                }
            }

            return response;
        }

        #endregion

        #region Version Upload

        [HttpPost]
        public LatestVersionResponse GetLatestVersion(LatestVersionRequest request)
        {
            LatestVersionResponse response = new LatestVersionResponse();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                var versions = db.TangoVersions.Where(x => x.MachineVersionGuid == request.MachineVersionGuid && request.Tag == x.Tag).ToList();

                if (versions.Count == 0)
                {
                    return new LatestVersionResponse()
                    {
                        Version = "0.0.0.0",
                        FirmwareVersion = "0.0.0.0"
                    };
                }

                var latestTangoVersion = versions.OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault();
                response.Version = latestTangoVersion.Version;
                response.FirmwareVersion = latestTangoVersion.FirmwareVersion;
            }

            return response;
        }

        [HttpPost]
        public GetTagsResponse GetTags(GetTagsRequest request)
        {
            GetTagsResponse response = new GetTagsResponse();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                var versions = db.TangoVersions.Where(x => x.MachineVersionGuid == request.MachineVersionGuid).ToList();

                if (versions.Count == 0)
                {
                    return new GetTagsResponse();
                }

                response.Tags = versions.Select(x => x.Tag).Where(x => x != null).Distinct().ToList();
            }

            return response;
        }

        [HttpPost]
        [JwtTokenFilter]
        public UploadVersionResponse UploadVersion(UploadVersionRequest request)
        {
            UploadVersionResponse response = new UploadVersionResponse();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                //Load relations first...
                db.Roles.ToList();
                db.Permissions.ToList();
                db.UsersRoles.ToList();
                db.RolesPermissions.ToList();

                var user_guid = RequestToken.Object.UserGuid;

                var user = new UserBuilder(db).Set(user_guid).WithRolesAndPermissions().Build();

                if (user != null && user.HasPermission(Permissions.PublishPPCVersions))
                {

                    if (!request.Tag.IsNotNullOrEmpty())
                    {
                        request.Tag = null;
                    }

                    var versions = db.TangoVersions.ToList().Where(x => x.MachineVersionGuid == request.MachineVersionGuid && x.Tag == request.Tag).OrderByDescending(x => Version.Parse(x.Version)).ToList();
                    var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == request.MachineVersionGuid);

                    TangoVersion latestVersion = new TangoVersion();
                    latestVersion.Version = "0.0.0.0";

                    if (versions.Count > 0)
                    {
                        latestVersion = versions.FirstOrDefault();
                    }

                    Version local_version = Version.Parse(request.Version);

                    if (local_version > Version.Parse(latestVersion.Version))
                    {
                        String newVersionFileName = $"{machine_version.Name} v{local_version.ToString(3) + (request.Tag.IsNotNullOrEmpty() ? $" [{request.Tag}]" : String.Empty)}.tup";

                        var manager = new BlobStorageManager();
                        var container = manager.GetContainer(MachineServiceConfig.TANGO_VERSIONS_CONTAINER);
                        var blob = container.CreateEmptyBlob(newVersionFileName);

                        response.Token = Guid.NewGuid().ToString();
                        response.BlobAddress = blob.GenerateWriteSignature(TimeSpan.FromMinutes(30));

                        PPCPendingUpload pending_upload = new PPCPendingUpload()
                        {
                            UserGuid = user.Guid,
                            Comments = request.Comments,
                            Token = response.Token,
                            Version = request.Version,
                            BlobName = blob.Name,
                            MachineVersionGuid = request.MachineVersionGuid,
                            FirmwareVersion = request.FirmwareVersion,
                            Tag = request.Tag,
                        };

                        if (request.WithInstaller)
                        {
                            String installerVersionFileName = Path.ChangeExtension(newVersionFileName, ".exe");
                            var installerBlob = container.CreateEmptyBlob(installerVersionFileName);
                            response.InstallerBlobAddress = installerBlob.GenerateWriteSignature(TimeSpan.FromMinutes(30));
                            pending_upload.InstallerBlobName = installerBlob.Name;
                        }

                        _pendingUploads.Add(pending_upload);
                    }
                    else
                    {
                        throw new ArgumentException("New version must be greater than latest version.");
                    }
                }
                else
                {
                    throw new AuthenticationException("Invalid user credentials.");
                }
            }

            return response;
        }

        [HttpPost]
        [JwtTokenFilter]
        public UploadCompletedResponse NotifyVersionUploadCompleted(UploadCompletedRequest request)
        {
            PPCPendingUpload upload = _pendingUploads.FirstOrDefault(x => x.Token == request.Token);

            if (upload != null)
            {
                _pendingUploads.RemoveAll(x => x.Token == upload.Token);

                using (ObservablesContext db = ObservablesWebContext.CreateContext())
                {
                    db.TangoVersions.Add(new TangoVersion()
                    {
                        Comments = upload.Comments,
                        BlobName = upload.BlobName,
                        InstallerBlobName = upload.InstallerBlobName,
                        UserGuid = upload.UserGuid,
                        Version = upload.Version,
                        MachineVersionGuid = upload.MachineVersionGuid,
                        FirmwareVersion = upload.FirmwareVersion,
                        Tag = upload.Tag,
                    });

                    db.SaveChanges();
                }

                return new UploadCompletedResponse();
            }
            else
            {
                throw new AuthenticationException("Invalid upload token.");
            }
        }

        [HttpPost]
        public MachineVersionsResponse GetMachineVersions(MachineVersionsRequest request)
        {
            using (var db = ObservablesWebContext.CreateContext())
            {
                return new MachineVersionsResponse()
                {
                    MachineVersions = db.MachineVersions.ToList().Select(x => MachineVersionDTO.FromObservable(x)).ToList(),
                };
            }
        }

        [HttpPost]
        public LoginResponse Login(LoginRequest request)
        {
            LoginResponse response = new LoginResponse();

            using (ObservablesContext db = ObservablesWebContext.CreateContext())
            {
                if (request.Mode == LoginMode.User)
                {
                    var authResult = _ad_manager.ValidateUserCredentials(request.Email, request.Password);

                    //if (!_ad_manager.CanUserAccessCurrentEnvironment(request.Email))
                    //{
                    //    throw new AuthenticationException($"You do not have permissions to access the {MachineServiceConfig.DEPLOYMENT_SLOT.ToDescription()} environment.");
                    //}

                    BasicHashGenerator hash = new BasicHashGenerator();
                    String pass = hash.Encrypt(request.Password);

                    var user = new UserBuilder(db).Set(x => x.Email.ToLower() == request.Email.ToLower() && x.Password == pass).Build();

                    if (user == null)
                    {
                        throw new AuthenticationException("Domain user found but the database entry validation failed.");
                    }

                    response.AccessToken = WebToken<TokenObject>.CreateNew(MachineServiceConfig.JWT_TOKEN_SECRET, new TokenObject()
                    {
                        Mode = LoginMode.User,
                        UserGuid = user.Guid,
                    }).AccessToken;
                }
                else if (request.Mode == LoginMode.Machine)
                {
                    var machine = db.Machines.SingleOrDefault(x => x.SerialNumber == request.SerialNumber || x.Guid == request.MachineGuid);

                    if (machine == null)
                    {
                        throw new AuthenticationException("Invalid machine serial number or id.");
                    }

                    response.AccessToken = WebToken<TokenObject>.CreateNew(MachineServiceConfig.JWT_TOKEN_SECRET, new TokenObject()
                    {
                        Mode = LoginMode.Machine,
                        MachineGuid = machine.Guid,
                    }).AccessToken;
                }
            }

            return response;
        }

        #endregion
    }
}