From 4954a924b8a5b8fd7a213a444027e74b936359be Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Wed, 16 Oct 2019 17:31:51 +0300 Subject: Added support for token authentication from MS to DB. --- .../Controllers/MachineStudioController.cs | 44 ++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) (limited to 'Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs') diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs index b718887af..dd8401570 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs +++ b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs @@ -22,6 +22,7 @@ using Tango.Web.Security; using Tango.Web.ActiveDirectory; using Tango.MachineService.Filters; using Tango.MachineService.Security; +using Tango.Web.SQLServer; namespace Tango.MachineService.Controllers { @@ -356,9 +357,26 @@ namespace Tango.MachineService.Controllers } } - return new LoginResponse() + Core.DataSource dataSource = null; + + if (MachineServiceConfig.USE_DB_ACCESS_TOKENS) { - DataSource = new Core.DataSource() + SQLServerManager sqlServer = new SQLServerManager(); + var accessToken = sqlServer.GetAccessToken(); + + dataSource = new Core.DataSource() + { + Address = MachineServiceConfig.DB_ADDRESS, + Catalog = MachineServiceConfig.DB_CATALOG, + Type = Core.DataSourceType.AccessToken, + IntegratedSecurity = false, + AccessToken = accessToken.AccessToken, + AccessTokenExpiration = accessToken.ExpiresOn.UtcDateTime + }; + } + else + { + dataSource = new Core.DataSource() { Address = MachineServiceConfig.DB_ADDRESS, Catalog = MachineServiceConfig.DB_CATALOG, @@ -366,8 +384,12 @@ namespace Tango.MachineService.Controllers IntegratedSecurity = false, UserName = request.Email, Password = request.Password, - }, + }; + } + return new LoginResponse() + { + DataSource = dataSource, AccessToken = WebToken.CreateNew(MachineServiceConfig.JWT_TOKEN_SECRET, new TokenObject() { UserGuid = user.Guid, @@ -377,6 +399,22 @@ namespace Tango.MachineService.Controllers }; } + [JwtTokenFilter] + public RefreshTokenResponse RefreshToken(RefreshTokenRequest request) + { + SQLServerManager sqlServer = new SQLServerManager(); + var accessToken = sqlServer.GetAccessToken(); + + //TokenManager tokenManager = new TokenManager(); + //tokenManager.UpdateToken(request.AccessToken, accessToken.AccessToken, accessToken.ExpiresOn.UtcDateTime); + + return new RefreshTokenResponse() + { + AccessToken = accessToken.AccessToken, + Expiration = accessToken.ExpiresOn.UtcDateTime, + }; + } + #endregion } } -- cgit v1.3.1 From 9259bc36791a7084ae33bcf0a698101ddb24d28f Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Thu, 5 Dec 2019 18:06:21 +0200 Subject: Integrated CDN downloads to PPC and MS. --- .../Web/CheckForUpdatesResponse.cs | 2 + .../Web/DownloadLatestVersionResponse.cs | 2 + .../ViewModels/UpdateViewVM.cs | 3 +- .../MachineSetup/MachineSetupManager.cs | 38 ++++--- .../MachineUpdate/MachineUpdateManager.cs | 32 ++++-- .../Tango.PPC.Common/Web/DownloadUpdateResponse.cs | 2 + .../Tango.PPC.Common/Web/MachineSetupResponse.cs | 2 + .../Tango.Transport/Tango.Transport.csproj | 7 +- .../Tango.Transport/Web/AutoFileDownloader.cs | 123 +++++++++++++++++++++ .../Tango.Transport/Web/IWebFileDownloader.cs | 15 +++ .../Tango.Transport/Web/StandardFileDownloader.cs | 77 +++++++++++++ .../Tango.Transport/Web/StorageBlobDownloader.cs | 23 +++- .../Web/StorageBlobProgressEventArgs.cs | 14 --- .../Tango.Transport/Web/StorageBlobUploader.cs | 4 +- .../Web/WebFileDownloaderProgressEventArgs.cs | 14 +++ .../Controllers/MachineStudioController.cs | 10 ++ .../Controllers/PPCController.cs | 12 +- .../Tango.MachineService/MachineServiceConfig.cs | 1 + .../Web/Tango.MachineService/Web.config | 1 + 19 files changed, 327 insertions(+), 55 deletions(-) create mode 100644 Software/Visual_Studio/Tango.Transport/Web/AutoFileDownloader.cs create mode 100644 Software/Visual_Studio/Tango.Transport/Web/IWebFileDownloader.cs create mode 100644 Software/Visual_Studio/Tango.Transport/Web/StandardFileDownloader.cs delete mode 100644 Software/Visual_Studio/Tango.Transport/Web/StorageBlobProgressEventArgs.cs create mode 100644 Software/Visual_Studio/Tango.Transport/Web/WebFileDownloaderProgressEventArgs.cs (limited to 'Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs') diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/CheckForUpdatesResponse.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/CheckForUpdatesResponse.cs index 51608e6c4..b78047c85 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/CheckForUpdatesResponse.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/CheckForUpdatesResponse.cs @@ -17,5 +17,7 @@ namespace Tango.MachineStudio.Common.Web public String Comments { get; set; } public String BlobAddress { get; set; } + + public String CdnAddress { get; set; } } } diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/DownloadLatestVersionResponse.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/DownloadLatestVersionResponse.cs index 3209b9a2f..60251d455 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/DownloadLatestVersionResponse.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/DownloadLatestVersionResponse.cs @@ -12,5 +12,7 @@ namespace Tango.MachineStudio.Common.Web public String Version { get; set; } public String BlobAddress { get; set; } + + public String CdnAddress { get; set; } } } diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/UpdateViewVM.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/UpdateViewVM.cs index 2ee8574b1..160041b5f 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/UpdateViewVM.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/UpdateViewVM.cs @@ -179,6 +179,7 @@ namespace Tango.MachineStudio.UI.ViewModels _updateInfo = new CheckForUpdatesResponse(); _updateInfo.BlobAddress = response.BlobAddress; _updateInfo.Version = response.Version; + _updateInfo.CdnAddress = response.CdnAddress; LatestVersion = _updateInfo.Version; StartUpdate(); @@ -270,7 +271,7 @@ namespace Tango.MachineStudio.UI.ViewModels { logManager.Log("Creating temporary file " + tempFile); - using (StorageBlobDownloader downloader = new StorageBlobDownloader(_updateInfo.BlobAddress, tempFile.Path)) + using (AutoFileDownloader downloader = new AutoFileDownloader(_updateInfo.BlobAddress, _updateInfo.CdnAddress, tempFile.Path)) { downloader.Progress += (x, e) => { diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs index 537e652e6..cce6c32ab 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs @@ -249,27 +249,29 @@ namespace Tango.PPC.Common.MachineSetup LogManager.Log("Downloading software package..."); - long fileSize = 0; - UpdateProgress("Downloading software package", "Downloading...", false); + AutoFileDownloader downloader = new AutoFileDownloader(setup_response.BlobAddress, setup_response.CdnAddress, tempFile); + await downloader.ResolveMode(); - await Task.Factory.StartNew(() => + if (downloader.Mode == AutoFileDownloader.DownloadMode.Standard) { - using (FileStreamWrapper fs = new FileStreamWrapper(tempFile.Path, FileMode.Create, (current) => - { - UpdateProgress("Downloading software package", "Downloading...", false, current, fileSize); - })) - { + LogManager.Log($"Connecting to storage CDN with address {downloader.Address}"); + } + else + { + LogManager.Log($"Connecting to storage blob with address {downloader.Address}"); + } - LogManager.Log($"Connecting to storage blob with address {setup_response.BlobAddress}"); - CloudBlockBlob blob = new CloudBlockBlob(new Uri(setup_response.BlobAddress)); - LogManager.Log("Fetching blob attributes..."); - blob.FetchAttributes(); - fileSize = blob.Properties.Length; - LogManager.Log("Download size: " + fileSize + " bytes."); - LogManager.Log("Starting blob download..."); - blob.DownloadToStream(fs); - } - }); + downloader.Progress += (x, e) => + { + UpdateProgress("Downloading software package", "Downloading...", false, e.Current, e.Total); + }; + + var size = await downloader.GetFileSize(); + LogManager.Log("Download size: " + size + " bytes."); + LogManager.Log("Starting file download..."); + await downloader.Download(); + + downloader.Dispose(); UpdateProgress("Downloading software package", "Extracting package..."); diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs index 8642c089f..333fb261d 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs @@ -329,24 +329,32 @@ namespace Tango.PPC.Common.MachineUpdate LogManager.Log("Downloading software package..."); - long fileSize = 0; UpdateProgress("Downloading software package", "Downloading...", false); - using (FileStreamWrapper fs = new FileStreamWrapper(tempFile.Path, FileMode.Create, (current) => + AutoFileDownloader downloader = new AutoFileDownloader(update_response.BlobAddress, update_response.CdnAddress, tempFile); + await downloader.ResolveMode(); + + if (downloader.Mode == AutoFileDownloader.DownloadMode.Standard) { - UpdateProgress("Downloading software package", "Downloading...", false, current, fileSize); - })) + LogManager.Log($"Connecting to storage CDN with address {downloader.Address}"); + } + else { - LogManager.Log($"Connecting to storage blob with address {update_response.BlobAddress}"); - CloudBlockBlob blob = new CloudBlockBlob(new Uri(update_response.BlobAddress)); - LogManager.Log("Fetching blob attributes..."); - blob.FetchAttributes(); - fileSize = blob.Properties.Length; - LogManager.Log("Download size: " + fileSize + " bytes."); - LogManager.Log("Starting blob download..."); - blob.DownloadToStream(fs); + LogManager.Log($"Connecting to storage blob with address {downloader.Address}"); } + downloader.Progress += (x, e) => + { + UpdateProgress("Downloading software package", "Downloading...", false, e.Current, e.Total); + }; + + var size = await downloader.GetFileSize(); + LogManager.Log("Download size: " + size + " bytes."); + LogManager.Log("Starting file download..."); + await downloader.Download(); + + downloader.Dispose(); + UpdateProgress("Downloading software package", "Extracting package..."); LogManager.Log("Extracting downloaded zip file..."); diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateResponse.cs index 4396ce67a..b092aedbe 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateResponse.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadUpdateResponse.cs @@ -16,6 +16,8 @@ namespace Tango.PPC.Common.Web public String BlobAddress { get; set; } + public String CdnAddress { get; set; } + public DataSource DataSource { get; set; } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupResponse.cs index 73cdc8609..714a413ab 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupResponse.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/MachineSetupResponse.cs @@ -16,6 +16,8 @@ namespace Tango.PPC.Common.Web public String BlobAddress { get; set; } + public String CdnAddress { get; set; } + public DataSource DataSource { get; set; } public String OSKey { get; set; } diff --git a/Software/Visual_Studio/Tango.Transport/Tango.Transport.csproj b/Software/Visual_Studio/Tango.Transport/Tango.Transport.csproj index 87af709e0..542a0a92d 100644 --- a/Software/Visual_Studio/Tango.Transport/Tango.Transport.csproj +++ b/Software/Visual_Studio/Tango.Transport/Tango.Transport.csproj @@ -109,14 +109,17 @@ + + + - + @@ -148,7 +151,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Transport/Web/AutoFileDownloader.cs b/Software/Visual_Studio/Tango.Transport/Web/AutoFileDownloader.cs new file mode 100644 index 000000000..1f45f10dc --- /dev/null +++ b/Software/Visual_Studio/Tango.Transport/Web/AutoFileDownloader.cs @@ -0,0 +1,123 @@ +using Microsoft.WindowsAzure.Storage.Blob; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Tango.Core.IO; + +namespace Tango.Transport.Web +{ + public class AutoFileDownloader : IWebFileDownloader + { + public enum DownloadMode + { + Standard, + Blob + } + + private bool _disposed; + private StorageBlobDownloader _blobDownloader; + private StandardFileDownloader _standardDownloader; + private bool _isCdnOK = false; + private long _fileSize = -1; + + public event EventHandler Progress; + + public String Address { get; private set; } + + public String FileName { get; private set; } + + public DownloadMode Mode { get; private set; } + + public AutoFileDownloader(String blobAddress, String cdnAddress, String fileName) + { + FileName = fileName; + + _blobDownloader = new StorageBlobDownloader(blobAddress, fileName); + _standardDownloader = new StandardFileDownloader(cdnAddress, fileName); + + _blobDownloader.Progress += OnProgress; + _standardDownloader.Progress += OnProgress; + } + + private void OnProgress(object sender, WebFileDownloaderProgressEventArgs e) + { + Progress?.Invoke(this, e); + } + + public async Task Download() + { + if (_disposed) + { + throw new ObjectDisposedException("The file downloader can only be used once."); + } + + if (_fileSize == -1) + { + await GetFileSize(); + } + + if (_isCdnOK) + { + await _standardDownloader.Download(); + } + else + { + await _blobDownloader.Download(); + } + } + + public async Task ResolveMode() + { + await GetFileSize(); + } + + public Task GetFileSize() + { + if (_fileSize == -1) + { + return Task.Factory.StartNew(() => + { + try + { + _fileSize = _standardDownloader.GetFileSize().Result; + _isCdnOK = true; + Mode = DownloadMode.Standard; + Address = _standardDownloader.Address; + return _fileSize; + } + catch + { + try + { + _fileSize = _blobDownloader.GetFileSize().Result; + Mode = DownloadMode.Blob; + Address = _blobDownloader.Blob.Uri.ToString(); + return _fileSize; + } + catch + { + throw new Exception("Invalid address for standard download or blob."); + } + } + }); + } + else + { + return Task.FromResult(_fileSize); + } + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + _blobDownloader.Dispose(); + _standardDownloader.Dispose(); + } + } + } +} diff --git a/Software/Visual_Studio/Tango.Transport/Web/IWebFileDownloader.cs b/Software/Visual_Studio/Tango.Transport/Web/IWebFileDownloader.cs new file mode 100644 index 000000000..2f65553d5 --- /dev/null +++ b/Software/Visual_Studio/Tango.Transport/Web/IWebFileDownloader.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Transport.Web +{ + public interface IWebFileDownloader : IDisposable + { + event EventHandler Progress; + Task Download(); + Task GetFileSize(); + } +} diff --git a/Software/Visual_Studio/Tango.Transport/Web/StandardFileDownloader.cs b/Software/Visual_Studio/Tango.Transport/Web/StandardFileDownloader.cs new file mode 100644 index 000000000..d254abd96 --- /dev/null +++ b/Software/Visual_Studio/Tango.Transport/Web/StandardFileDownloader.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Transport.Web +{ + public class StandardFileDownloader : IWebFileDownloader + { + private WebClient _client; + private TaskCompletionSource _completionSource; + + public String Address { get; private set; } + + public String FileName { get; private set; } + + public event EventHandler Progress; + + public StandardFileDownloader(String address, String fileName) + { + Address = address; + FileName = fileName; + _client = new WebClient(); + _client.DownloadProgressChanged += _client_DownloadProgressChanged; + _client.DownloadFileCompleted += _client_DownloadFileCompleted; + + _completionSource = new TaskCompletionSource(); + } + + private void _client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) + { + if (e.Error != null) + { + _completionSource.SetException(e.Error); + } + else + { + _completionSource.SetResult(true); + } + } + + public Task Download() + { + _client.DownloadFileAsync(new Uri(Address), FileName); + return _completionSource.Task; + } + + private void _client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) + { + Progress?.Invoke(this, new WebFileDownloaderProgressEventArgs() + { + Current = e.BytesReceived, + Total = e.TotalBytesToReceive, + }); + } + + public Task GetFileSize() + { + return Task.Factory.StartNew(() => + { + using (var client = new WebClient()) + { + client.OpenRead(Address); + Int64 bytes_total = Convert.ToInt64(client.ResponseHeaders["Content-Length"]); + return bytes_total; + } + }); + } + + public void Dispose() + { + _client.Dispose(); + } + } +} diff --git a/Software/Visual_Studio/Tango.Transport/Web/StorageBlobDownloader.cs b/Software/Visual_Studio/Tango.Transport/Web/StorageBlobDownloader.cs index 603463823..8977c5530 100644 --- a/Software/Visual_Studio/Tango.Transport/Web/StorageBlobDownloader.cs +++ b/Software/Visual_Studio/Tango.Transport/Web/StorageBlobDownloader.cs @@ -9,20 +9,21 @@ using Tango.Core.IO; namespace Tango.Transport.Web { - public class StorageBlobDownloader : IDisposable + public class StorageBlobDownloader : IWebFileDownloader { private bool _disposed; private FileStreamWrapper _stream; private long _fileSize; + private String _fileName; public CloudBlockBlob Blob { get; private set; } - public event EventHandler Progress; + public event EventHandler Progress; public StorageBlobDownloader(CloudBlockBlob blob, String fileName) { Blob = blob; - _stream = new FileStreamWrapper(fileName, FileMode.Create, OnProgress); + _fileName = fileName; } public StorageBlobDownloader(String blobAddress, String fileName) : this(new CloudBlockBlob(new Uri(blobAddress)), fileName) @@ -32,7 +33,7 @@ namespace Tango.Transport.Web private void OnProgress(long current) { - Progress?.Invoke(this, new StorageBlobProgressEventArgs() + Progress?.Invoke(this, new WebFileDownloaderProgressEventArgs() { Current = current, Total = _fileSize, @@ -49,6 +50,8 @@ namespace Tango.Transport.Web await Blob.FetchAttributesAsync(); _fileSize = Blob.Properties.Length; + _stream = new FileStreamWrapper(_fileName, FileMode.Create, OnProgress); + await Blob.DownloadToStreamAsync(_stream); Dispose(); } @@ -58,8 +61,18 @@ namespace Tango.Transport.Web if (!_disposed) { _disposed = true; - _stream.Dispose(); + + if (_stream != null) + { + _stream.Dispose(); + } } } + + public async Task GetFileSize() + { + await Blob.FetchAttributesAsync(); + return Blob.Properties.Length; + } } } diff --git a/Software/Visual_Studio/Tango.Transport/Web/StorageBlobProgressEventArgs.cs b/Software/Visual_Studio/Tango.Transport/Web/StorageBlobProgressEventArgs.cs deleted file mode 100644 index ae48e34cf..000000000 --- a/Software/Visual_Studio/Tango.Transport/Web/StorageBlobProgressEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tango.Transport.Web -{ - public class StorageBlobProgressEventArgs : EventArgs - { - public long Total { get; set; } - public long Current { get; set; } - } -} diff --git a/Software/Visual_Studio/Tango.Transport/Web/StorageBlobUploader.cs b/Software/Visual_Studio/Tango.Transport/Web/StorageBlobUploader.cs index 8d645f33f..00600c679 100644 --- a/Software/Visual_Studio/Tango.Transport/Web/StorageBlobUploader.cs +++ b/Software/Visual_Studio/Tango.Transport/Web/StorageBlobUploader.cs @@ -16,7 +16,7 @@ namespace Tango.Transport.Web public CloudBlockBlob Blob { get; private set; } - public event EventHandler Progress; + public event EventHandler Progress; public StorageBlobUploader(CloudBlockBlob blob, String fileName) { @@ -31,7 +31,7 @@ namespace Tango.Transport.Web private void OnProgress(long current) { - Progress?.Invoke(this, new StorageBlobProgressEventArgs() + Progress?.Invoke(this, new WebFileDownloaderProgressEventArgs() { Current = current, Total = _stream.Length, diff --git a/Software/Visual_Studio/Tango.Transport/Web/WebFileDownloaderProgressEventArgs.cs b/Software/Visual_Studio/Tango.Transport/Web/WebFileDownloaderProgressEventArgs.cs new file mode 100644 index 000000000..570c4058a --- /dev/null +++ b/Software/Visual_Studio/Tango.Transport/Web/WebFileDownloaderProgressEventArgs.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Transport.Web +{ + public class WebFileDownloaderProgressEventArgs : EventArgs + { + public long Total { get; set; } + public long Current { get; set; } + } +} diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs index dd8401570..2eeaa6e0e 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs +++ b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs @@ -99,6 +99,11 @@ namespace Tango.MachineService.Controllers response.BlobAddress = blob.GenerateReadSignature(TimeSpan.FromMinutes(60)); + if (!String.IsNullOrWhiteSpace(MachineServiceConfig.CDN_ENDPOINT)) + { + response.CdnAddress = MachineServiceConfig.CDN_ENDPOINT + blob.Uri.AbsolutePath; + } + response.IsUpdateAvailable = true; response.Version = latestVersion.Version; response.Comments = latestVersion.Comments; @@ -133,6 +138,11 @@ namespace Tango.MachineService.Controllers var container = manager.GetContainer(MachineServiceConfig.MACHINE_STUDIO_VERSIONS_CONTAINER); var blob = container.GetBlockBlobReference(latestVersion.BlobName); + if (!String.IsNullOrWhiteSpace(MachineServiceConfig.CDN_ENDPOINT)) + { + response.CdnAddress = MachineServiceConfig.CDN_ENDPOINT + blob.Uri.AbsolutePath; + } + response.BlobAddress = blob.GenerateReadSignature(TimeSpan.FromMinutes(60)); response.Version = latestVersion.Version; } diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs index 4c39aad80..f0239978f 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs +++ b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs @@ -32,7 +32,7 @@ namespace Tango.MachineService.Controllers private static List _pendingUploads; private static List _pendingUpdates; private ActiveDirectoryManager _ad_manager; - private const int SQL_TEMP_CREDENTIALS_EXP_MINUTS = 20; + private const int SQL_TEMP_CREDENTIALS_EXP_MINUTS = 20; public class TokenObject { @@ -114,6 +114,11 @@ namespace Tango.MachineService.Controllers 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()) @@ -197,6 +202,11 @@ namespace Tango.MachineService.Controllers 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()) diff --git a/Software/Visual_Studio/Web/Tango.MachineService/MachineServiceConfig.cs b/Software/Visual_Studio/Web/Tango.MachineService/MachineServiceConfig.cs index e8165a4a6..014ef68ba 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/MachineServiceConfig.cs +++ b/Software/Visual_Studio/Web/Tango.MachineService/MachineServiceConfig.cs @@ -16,5 +16,6 @@ namespace Tango.MachineService public static String REFRESH_TOKENS_TABLE_NAME => ConfigurationManager.AppSettings[nameof(REFRESH_TOKENS_TABLE_NAME)].ToString(); public static String REFRESH_TOKENS_TABLE_PARTITION => ConfigurationManager.AppSettings[nameof(REFRESH_TOKENS_TABLE_PARTITION)].ToString(); public static bool USE_DB_ACCESS_TOKENS => bool.Parse(ConfigurationManager.AppSettings[nameof(USE_DB_ACCESS_TOKENS)].ToString()); + public static String CDN_ENDPOINT => ConfigurationManager.AppSettings[nameof(CDN_ENDPOINT)].ToString(); } } \ No newline at end of file diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Web.config b/Software/Visual_Studio/Web/Tango.MachineService/Web.config index dce9e1fb9..97a0d511f 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Web.config +++ b/Software/Visual_Studio/Web/Tango.MachineService/Web.config @@ -30,6 +30,7 @@ + + + + + + Synchronize Jobs + + + + Synchronize your jobs with twine's cloud services. + + + + + + + + + Synchronize Diagnostics Data + + + + Help us improve your experience using this system. + + + + + + + + + + Once enabled, synchronization occurres automatically in the background. you can choose to synchronize right now. + + + Synchronize Now + + diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Images/sync.png b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Images/sync.png new file mode 100644 index 000000000..46059c5c0 Binary files /dev/null and b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Images/sync.png differ diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Tango.PPC.Technician.csproj b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Tango.PPC.Technician.csproj index 192b9ae11..e4261334a 100644 --- a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Tango.PPC.Technician.csproj +++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Tango.PPC.Technician.csproj @@ -98,10 +98,14 @@ + CatalogView.xaml + + SynchronizationView.xaml + PackagesView.xaml @@ -191,6 +195,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -234,5 +242,8 @@ + + + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModelLocator.cs b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModelLocator.cs index c79a10e79..05a04e2a6 100644 --- a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModelLocator.cs +++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModelLocator.cs @@ -21,6 +21,7 @@ namespace Tango.PPC.Technician TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); + TangoIOC.Default.Register(); } /// @@ -88,5 +89,16 @@ namespace Tango.PPC.Technician return TangoIOC.Default.GetInstance(); } } + + /// + /// Gets the synchronization view vm. + /// + public static SynchronizationViewVM SynchronizationViewVM + { + get + { + return TangoIOC.Default.GetInstance(); + } + } } } diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModels/SynchronizationViewVM.cs b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModels/SynchronizationViewVM.cs new file mode 100644 index 000000000..8036b99a4 --- /dev/null +++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModels/SynchronizationViewVM.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core.Commands; +using Tango.PPC.Common; + +namespace Tango.PPC.Technician.ViewModels +{ + public class SynchronizationViewVM : PPCViewModel + { + public RelayCommand SynchronizeCommand { get; set; } + + public SynchronizationViewVM() + { + SynchronizeCommand = new RelayCommand(Synchronize, () => !MachineDataSynchronizer.IsSynchronizing); + } + + public override void OnApplicationStarted() + { + + } + + public override void OnApplicationReady() + { + base.OnApplicationReady(); + MachineDataSynchronizer.SynchronizationStarted += (_, __) => InvalidateRelayCommands(); + MachineDataSynchronizer.SynchronizationEnded += (_, __) => InvalidateRelayCommands(); + } + + private async void Synchronize() + { + try + { + await MachineDataSynchronizer.Synchronize(); + } + catch { } + } + } +} diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModels/SystemViewVM.cs b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModels/SystemViewVM.cs index b46e566d1..13aeeb671 100644 --- a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModels/SystemViewVM.cs +++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/ViewModels/SystemViewVM.cs @@ -265,11 +265,11 @@ namespace Tango.PPC.Technician.ViewModels { using (ObservablesContext db = ObservablesContext.CreateDefault()) { - var jobs = await db.Jobs.Include(x => x.JobRuns).Where(x => x.MachineGuid == MachineProvider.Machine.Guid).ToListAsync(); + var jobRuns = await db.JobRuns.Where(x => x.MachineGuid == MachineProvider.Machine.Guid).ToListAsync(); - TotalDyeTime = TimeSpan.FromHours(jobs.SelectMany(x => x.JobRuns).Select(x => x.EndDate - x.StartDate).Sum(x => x.TotalHours)).ToString(@"hh\:mm\:ss"); + TotalDyeTime = TimeSpan.FromHours(jobRuns.Select(x => x.EndDate - x.StartDate).Sum(x => x.TotalHours)).ToString(@"hh\:mm\:ss"); - int meters = (int)jobs.SelectMany(x => x.JobRuns).Select(x => x.EndPosition).Sum(); + int meters = (int)jobRuns.Select(x => x.EndPosition).Sum(); TotalDyeMeters = $"{meters.ToString("N0")} meters"; } } diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/CatalogView.xaml b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/CatalogView.xaml index c13c3e3eb..b0a86d938 100644 --- a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/CatalogView.xaml +++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/CatalogView.xaml @@ -50,7 +50,7 @@ Logging - + Display and investigate issues using application and embedded device logs. @@ -62,7 +62,7 @@ System - + Display system properties, perform system actions, reset, shutdown etc... @@ -74,7 +74,7 @@ Dispensers - + Perform manual dispensers homing priming. @@ -86,12 +86,24 @@ Installed Packages - + View the history of update packages installation. + + + + + + Synchronization + + View the current status and history of synchronization operations to the machine service. + + + + diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/MainView.xaml b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/MainView.xaml index 733a2d713..2acadb32d 100644 --- a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/MainView.xaml +++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/MainView.xaml @@ -18,6 +18,7 @@ + diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/SynchronizationView.xaml b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/SynchronizationView.xaml new file mode 100644 index 000000000..a0021d40e --- /dev/null +++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/SynchronizationView.xaml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + Synchronization + + + + + + + + Synchronize Now + Synchronization occurres automatically in the background. You can choose to synchronize now. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/SynchronizationView.xaml.cs b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/SynchronizationView.xaml.cs new file mode 100644 index 000000000..7193d66ff --- /dev/null +++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.Technician/Views/SynchronizationView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Tango.PPC.Technician.Views +{ + /// + /// Interaction logic for SynchronizationView.xaml + /// + public partial class SynchronizationView : UserControl + { + public SynchronizationView() + { + InitializeComponent(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs index 1474eaa04..8f1a80d37 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Application/IPPCApplicationManager.cs @@ -95,6 +95,11 @@ namespace Tango.PPC.Common.Application /// Version Version { get; } + /// + /// Gets the firmware version. + /// + Version FirmwareVersion { get; } + /// /// Gets the application build date. /// diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/DefaultMachineProvider.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/DefaultMachineProvider.cs index 8cccc43a7..eae09a7e7 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/DefaultMachineProvider.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Connection/DefaultMachineProvider.cs @@ -134,6 +134,12 @@ namespace Tango.PPC.Common.Connection try { await MachineOperator.Connect(); + + if (MachineOperator.DeviceInformation != null) + { + settings.FirmwareVersion = MachineOperator.DeviceInformation.Version; + settings.Save(); + } } catch (Exception) { @@ -156,6 +162,12 @@ namespace Tango.PPC.Common.Connection try { await MachineOperator.Connect(); + + if (MachineOperator.DeviceInformation != null) + { + settings.FirmwareVersion = MachineOperator.DeviceInformation.Version; + settings.Save(); + } } catch (Exception) { @@ -186,6 +198,12 @@ namespace Tango.PPC.Common.Connection LogManager.Log("Connecting machine operator..."); await MachineOperator.Connect(); + if (MachineOperator.DeviceInformation != null) + { + settings.FirmwareVersion = MachineOperator.DeviceInformation.Version; + settings.Save(); + } + await Task.Delay(1000); await MachineOperator.UploadHardwareConfiguration(Machine.Configuration.HardwareVersion, Machine.Configuration); } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs index f1c722d96..dfa9b833b 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineSetup/MachineSetupManager.cs @@ -44,6 +44,7 @@ namespace Tango.PPC.Common.MachineSetup private IOperationSystemManager _windows_manager; private PPCWebClient _client; private List _logs; + private bool _isUpdating; #region Events @@ -93,7 +94,10 @@ namespace Tango.PPC.Common.MachineSetup private void LogManager_NewLog(object sender, LogItemBase e) { - _logs.Add(e); + if (_isUpdating) + { + _logs.Add(e); + } } #endregion @@ -136,6 +140,8 @@ namespace Tango.PPC.Common.MachineSetup LogManager.Log(xx, "Error notifying setup completed."); } } + + _isUpdating = false; } private async void OnCompleted(MachineSetupResult result, TaskCompletionSource completionSource, MachineSetupResponse response) @@ -157,6 +163,8 @@ namespace Tango.PPC.Common.MachineSetup LogManager.Log(xx, "Error notifying setup completed."); } } + + _isUpdating = false; } private String GetLogsStringAndClear() @@ -186,6 +194,8 @@ namespace Tango.PPC.Common.MachineSetup try { + _isUpdating = true; + LogManager.Log($"Starting machine setup for serial number {serialNumber}..."); var machineServiceAddress = SettingsManager.Default.GetOrCreate().GetMachineServiceAddress(); diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs index 7e742ceb6..30abb561f 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/MachineUpdate/MachineUpdateManager.cs @@ -880,6 +880,8 @@ namespace Tango.PPC.Common.MachineUpdate UpdateDBRequest request = new UpdateDBRequest(); request.SerialNumber = serialNumber; + request.ApplicationVersion = _app_manager.Version.ToString(); + request.FirmwareVersion = _app_manager.FirmwareVersion.ToString(); UpdateDBResponse update_response = null; diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs index d791d10d3..8805b75e4 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCSettings.cs @@ -179,6 +179,26 @@ namespace Tango.PPC.Common /// public String PreviousApplicationVersion { get; set; } + /// + /// Gets or sets a value indicating whether synchronize jobs with twine server. + /// + public bool SynchronizeJobs { get; set; } + + /// + /// Gets or sets a value indicating whether synchronize diagnostics data. + /// + public bool SynchronizeDiagnostics { get; set; } + + /// + /// Gets or sets the synchronization interval. + /// + public TimeSpan SynchronizationInterval { get; set; } + + /// + /// Gets or sets the known firmware version. + /// + public String FirmwareVersion { get; set; } + /// /// Gets the machine service address. /// @@ -214,6 +234,10 @@ namespace Tango.PPC.Common SupportedColorSpaces = new List(); SupportedJobTypes = new List(); PreviousApplicationVersion = "1.0.0.0"; + SynchronizeJobs = true; + SynchronizeDiagnostics = true; + SynchronizationInterval = TimeSpan.FromMinutes(60); + FirmwareVersion = "1.0.0.0"; } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCViewModel.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCViewModel.cs index 19bc6cd67..7992e6672 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCViewModel.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCViewModel.cs @@ -17,6 +17,7 @@ using Tango.PPC.Common.Notifications; using Tango.PPC.Common.Printing; using Tango.PPC.Common.RemoteAssistance; using Tango.PPC.Common.Storage; +using Tango.PPC.Common.Synchronization; using Tango.Settings; using Tango.SharedUI; using static Tango.SharedUI.Controls.NavigationControl; @@ -109,6 +110,12 @@ namespace Tango.PPC.Common [TangoInject] public IEventLogger EventLogger { get; set; } + /// + /// Gets or sets the machine data synchronizer. + /// + [TangoInject] + public IMachineDataSynchronizer MachineDataSynchronizer { get; set; } + private PPCSettings _settings; /// /// Gets the main PPC settings. diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/DefaultMachineDataSynchronizer.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/DefaultMachineDataSynchronizer.cs new file mode 100644 index 000000000..8260eb4b3 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/DefaultMachineDataSynchronizer.cs @@ -0,0 +1,554 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Timers; +using Tango.BL; +using Tango.PPC.Common.Web; +using System.Data.Entity; +using Tango.BL.DTO; +using Tango.PPC.Common.Connection; +using Tango.BL.Builders; +using Tango.Settings; +using Tango.Core; +using Tango.PPC.Common.Authentication; +using Tango.Logging; +using System.Diagnostics; +using Tango.BL.Enumerations; +using Tango.PPC.Common.Application; +using Tango.Core.DI; + +namespace Tango.PPC.Common.Synchronization +{ + public class DefaultMachineDataSynchronizer : ExtendedObject, IMachineDataSynchronizer + { + private Timer _synchTimer; + private PPCWebClient client; + private IMachineProvider _machineProvider; + private IAuthenticationProvider _authenticationProvider; + private List _logs; + private bool _synchronizedOnce; + + [TangoInject(TangoInjectMode.WhenAvailable)] + private IPPCApplicationManager _appManager; + + public event EventHandler CurrentStatusChanged; + public event EventHandler SynchronizationStarted; + public event EventHandler SynchronizationEnded; + + public int MaxJobs { get; set; } + public int MaxJobRuns { get; set; } + public int MaxMachinesEvents { get; set; } + + private SynchronizationStatus _currentStatus; + public SynchronizationStatus CurrentStatus + { + get { return _currentStatus; } + private set { _currentStatus = value; RaisePropertyChangedAuto(); } + } + + private SynchronizationStatus _lastStatus; + public SynchronizationStatus LastStatus + { + get { return _lastStatus; } + private set { _lastStatus = value; RaisePropertyChangedAuto(); } + } + + public SynchronizedObservableCollection StatusHistory { get; private set; } + + public TimeSpan Interval { get; set; } + + private bool _isEnabled; + public bool IsEnabled + { + get { return _isEnabled; } + set { _isEnabled = value; OnEnableChanged(); RaisePropertyChangedAuto(); } + } + + private bool _isSynchronizing; + public bool IsSynchronizing + { + get { return _isSynchronizing; } + set { _isSynchronizing = value; RaisePropertyChangedAuto(); } + } + + public DefaultMachineDataSynchronizer() + { + StatusHistory = new SynchronizedObservableCollection(); + + MaxJobs = 10; + MaxJobRuns = 100; + MaxMachinesEvents = 100; + + var settings = SettingsManager.Default.GetOrCreate(); + Interval = settings.SynchronizationInterval; + + _synchTimer = new Timer(Interval.TotalMilliseconds); + _synchTimer.Elapsed += _synchTimer_Elapsed; + _synchTimer.Enabled = true; + + ExecuteNewStatus(TimeSpan.FromMinutes(2)); + LastStatus = CurrentStatus; + } + + public DefaultMachineDataSynchronizer(PPCWebClient ppcWebClient, IMachineProvider machineProvider, IAuthenticationProvider authenticationProvider) : this() + { + _logs = new List(); + _machineProvider = machineProvider; + client = new PPCWebClient(ppcWebClient, TimeSpan.FromMinutes(10)); + _authenticationProvider = authenticationProvider; + + LogManager.NewLog += LogManager_NewLog; + } + + private void LogManager_NewLog(object sender, LogItemBase e) + { + if (IsSynchronizing) + { + _logs.Add(e); + } + } + + private String GetLogsStringAndClear() + { + String logsString = String.Join(Environment.NewLine, _logs.ToList().Select(x => x.ToString())); + _logs.Clear(); + return logsString; + } + + private void OnEnableChanged() + { + _synchTimer.Interval = Interval.TotalMilliseconds; + + if (IsEnabled) + { + CurrentStatus.State = SynchronizationState.Pending; + } + else + { + CurrentStatus.State = SynchronizationState.Disabled; + } + } + + private void _synchTimer_Elapsed(object sender, ElapsedEventArgs e) + { + _synchTimer.Interval = Interval.TotalMilliseconds; + + try + { + Synchronize().GetAwaiter().GetResult(); + } + catch { } + } + + private async Task CreateUploadMachineDataRequest(bool syncJobs, bool syncDiagnostics) + { + UploadMachineDataRequest request = new UploadMachineDataRequest(); + + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + if (syncJobs) + { + LogManager.Log("Checking Jobs..."); + + var jobs = await new JobsCollectionBuilder(db).Set(x => !x.IsSynchronized).WithSegments().WithBrushStops().Query(x => x.Take(MaxJobs).OrderByDescending(z => z.LastUpdated)).BuildListAsync(); + List dtos = new List(); + + foreach (var job in jobs) + { + var dto = JobDTO.FromObservable(job); + request.Jobs.Add(dto); + } + } + + if (syncDiagnostics) + { + LogManager.Log("Checking Job Runs..."); + + var jobRuns = await db.JobRuns.Where(x => !x.IsSynchronized).Take(MaxJobRuns).OrderByDescending(x => x.LastUpdated).ToListAsync(); + List dtos = new List(); + + foreach (var jobRun in jobRuns) + { + var dto = JobRunDTO.FromObservable(jobRun); + request.JobRuns.Add(dto); + } + } + + if (syncDiagnostics) + { + LogManager.Log("Checking Events..."); + + var machineEvents = await db.MachinesEvents.Where(x => !x.IsSynchronized).Take(MaxMachinesEvents).OrderByDescending(x => x.LastUpdated).ToListAsync(); + List dtos = new List(); + + foreach (var machineEvent in machineEvents) + { + machineEvent.IsSynchronized = true; + var dto = MachinesEventDTO.FromObservable(machineEvent); + request.MachineEvents.Add(dto); + } + } + } + + return request; + } + + private async Task FinalizeMachineDataUpload(UploadMachineDataRequest request, UploadMachineDataResponse response) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + //Finalize jobs + foreach (var job in request.Jobs) + { + var failedJob = response.FailedJobs.SingleOrDefault(x => x.Guid == job.Guid); + + if (failedJob == null) + { + var dbJob = await db.Jobs.SingleOrDefaultAsync(x => x.Guid == job.Guid); + dbJob.IsSynchronized = true; + } + else + { + LogManager.Log($"Synchronization Error - Job '{job.Name}' cannot be stored on the server due to the following reason:\n{failedJob.Reason}", LogCategory.Error); + } + } + + //Finalize job runs + foreach (var jobRun in request.JobRuns) + { + var failedJobRun = response.FailedJobRuns.SingleOrDefault(x => x.Guid == jobRun.Guid); + + if (failedJobRun == null) + { + var dbJobRun = await db.JobRuns.SingleOrDefaultAsync(x => x.Guid == jobRun.Guid); + dbJobRun.IsSynchronized = true; + } + else + { + LogManager.Log($"Synchronization Error - JobRun '{jobRun.ID}' cannot be stored on the server due to the following reason:\n{failedJobRun.Reason}", LogCategory.Error); + } + } + + //Finalize machine events + foreach (var machineEvent in request.MachineEvents) + { + var failedMachineEvent = response.FailedMachineEvents.SingleOrDefault(x => x.Guid == machineEvent.Guid); + + if (failedMachineEvent == null) + { + var dbMachineEvent = await db.MachinesEvents.SingleOrDefaultAsync(x => x.Guid == machineEvent.Guid); + dbMachineEvent.IsSynchronized = true; + } + else + { + LogManager.Log($"Synchronization Error - Event '{machineEvent.ID}' cannot be stored on the server due to the following reason:\n{failedMachineEvent.Reason}", LogCategory.Error); + } + } + + await db.SaveChangesAsync(); + } + } + + private async Task DownloadMachineData(bool syncJobs, bool syncDiagnostics) + { + return await client.DownloadMachineData(new DownloadMachineDataRequest() + { + RequestJobs = syncJobs, + RequestJobRuns = syncDiagnostics, + RequestMachineEvents = syncDiagnostics, + MaxJobs = MaxJobs, + MaxJobRuns = MaxJobRuns, + MaxMachinesEvents = MaxMachinesEvents, + }); + } + + private async Task InsertReplaceMachineData(DownloadMachineDataResponse response) + { + NotifyMachineDataDownloadCompletedRequest request = new NotifyMachineDataDownloadCompletedRequest(); + + //Insert/Replace Jobs. + if (response.Jobs.Count > 0) + { + LogManager.Log("Inserting/Replacing Jobs..."); + } + foreach (var dto in response.Jobs) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + try + { + var job = dto.ToObservable(); + + job.ID = 0; + job.UserGuid = _authenticationProvider.CurrentUser.Guid; + job.CustomerGuid = null; + job.IsSynchronized = true; + + var existingJob = await db.Jobs.SingleOrDefaultAsync(x => x.Guid == job.Guid); + + if (existingJob == null) + { + db.Jobs.Add(job); + await db.SaveChangesAsync(); + } + else if (job.LastUpdated > existingJob.LastUpdated) + { + existingJob.Delete(db); + db.Jobs.Add(job); + await db.SaveChangesAsync(); + } + + request.SynchronizedJobs.Add(job.Guid); + } + catch (Exception ex) + { + LogManager.Log($"Synchronization Error - Job '{dto.Name}' cannot be stored locally due to the following reason:\n{ex.FlattenMessage()}", LogCategory.Error); + } + } + } + + //Insert JobRuns. + if (response.JobRuns.Count > 0) + { + LogManager.Log("Inserting/Replacing Job Runs..."); + } + foreach (var dto in response.JobRuns) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + try + { + var run = dto.ToObservable(); + run.ID = 0; + run.IsSynchronized = true; + + if (await db.JobRuns.SingleOrDefaultAsync(x => x.Guid == run.Guid) == null) + { + db.JobRuns.Add(run); + await db.SaveChangesAsync(); + } + + request.SynchronizedJobRuns.Add(run.Guid); + } + catch (Exception ex) + { + LogManager.Log($"Synchronization Error - JobRun '{dto.ID}' cannot be stored locally due to the following reason:\n{ex.FlattenMessage()}", LogCategory.Error); + } + } + } + + //Insert MachineEvents. + if (response.MachineEvents.Count > 0) + { + LogManager.Log("Inserting/Replacing Events..."); + } + foreach (var dto in response.MachineEvents) + { + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + try + { + var ev = dto.ToObservable(); + ev.ID = 0; + ev.UserGuid = _authenticationProvider.CurrentUser.Guid; + ev.IsSynchronized = true; + + if (await db.MachinesEvents.SingleOrDefaultAsync(x => x.Guid == ev.Guid) == null) + { + db.MachinesEvents.Add(ev); + await db.SaveChangesAsync(); + } + + request.SynchronizedMachineEvents.Add(ev.Guid); + } + catch (Exception ex) + { + LogManager.Log($"Synchronization Error - Event '{dto.ID}' cannot be stored locally due to the following reason:\n{ex.FlattenMessage()}", LogCategory.Error); + } + } + } + + return request; + } + + public async Task Synchronize() + { + _synchronizedOnce = true; + + if (!IsEnabled || IsSynchronizing) return; + + var settings = SettingsManager.Default.GetOrCreate(); + + var syncJobs = settings.SynchronizeJobs; + var syncDiagnostics = settings.SynchronizeDiagnostics; + + if (!syncJobs && !syncDiagnostics) return; + + IsSynchronizing = true; + SynchronizationStarted?.Invoke(this, new EventArgs()); + + _logs.Clear(); + + _synchTimer.Stop(); + + LogManager.Log("Starting machine data synchronization..."); + LogManager.Log($"Synchronization interval: {Interval}."); + + CurrentStatus.StartDateTime = DateTime.Now; + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Starting synchronization..."); + + Stopwatch watch = new Stopwatch(); + watch.Start(); + + String notifyToken = null; + + int newChangedJobs = 0; + int newJobRuns = 0; + int newMachineEvents = 0; + + try + { + LogManager.Log("Authenticating with machine service..."); + + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Authenticating with machine service..."); + + if (!this.client.IsAuthenticated) + { + await this.client.Login(new LoginRequest() + { + Mode = LoginMode.Machine, + SerialNumber = _machineProvider.Machine.SerialNumber, + }); + } + + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Preparing machine data for upload..."); + LogManager.Log("Preparing machine data for upload..."); + var request = await CreateUploadMachineDataRequest(syncJobs, syncDiagnostics); + request.ApplicationVersion = _appManager.Version.ToString(); + request.FirmwareVersion = _appManager.FirmwareVersion.ToString(); + + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Uploading machine data..."); + LogManager.Log($"Uploading machine data:\nJobs: {request.Jobs.Count}\nJob Runs: {request.JobRuns.Count}\nEvents: {request.MachineEvents.Count}"); + var response = await this.client.UploadMachineData(request); + notifyToken = response.NotifyCompletedToken; + LogManager.Log($"Upload response received:\nFailed Jobs: {response.FailedJobs.Count}\nFailed Job Runs: {response.FailedJobRuns.Count}\nFailed Events: {response.FailedMachineEvents.Count}"); + LogManager.Log("Finalizing upload..."); + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Finalizing upload..."); + await FinalizeMachineDataUpload(request, response); + + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Downloading machine data from service..."); + LogManager.Log("Downloading machine data from server..."); + var downloadResponse = await DownloadMachineData(syncJobs, syncDiagnostics); + + newChangedJobs = downloadResponse.Jobs.Count; + newJobRuns = downloadResponse.JobRuns.Count; + newMachineEvents = downloadResponse.MachineEvents.Count; + + LogManager.Log($"Download response received:\nJobs: {downloadResponse.Jobs.Count}\nJob Runs: {downloadResponse.JobRuns.Count}\nEvents: {downloadResponse.MachineEvents.Count}"); + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Updating local database..."); + LogManager.Log("Updating local database..."); + var notifyRequest = await InsertReplaceMachineData(downloadResponse); + LogManager.Log($"Finalizing download:\nSynchronized Jobs: {notifyRequest.SynchronizedJobs.Count}\nSynchronized Job Runs: {notifyRequest.SynchronizedJobRuns.Count}\nSynchronized Events: {notifyRequest.SynchronizedMachineEvents.Count}"); + UpdateCurrentStatus(SynchronizationState.Synchronizing, "Finalizing download..."); + var notifyResponse = await this.client.NotifyMachineDataDownloadCompleted(notifyRequest); + + if (notifyToken != null) + { + try + { + await client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest() + { + Token = notifyToken, + Status = TangoUpdateStatuses.SynchronizationCompleted, + }); + } + catch (Exception ex) + { + LogManager.Log(ex, "Synchronization completed successfully but an error occurred when trying to notify about the completion."); + } + } + + LogManager.Log("Machine data synchronization completed successfully."); + UpdateCurrentStatus(SynchronizationState.Completed, "Synchronization completed successfully.", null, watch.Elapsed); + } + catch (Exception ex) + { + if (notifyToken != null) + { + try + { + await client.NotifyUpdateCompleted(new MachineUpdateCompletedRequest() + { + Token = notifyToken, + Status = TangoUpdateStatuses.SynchronizationFailed, + FailedReason = ex.FlattenMessage(), + FailedLog = GetLogsStringAndClear(), + }); + } + catch (Exception ee) + { + LogManager.Log(ee, "Synchronization completed successfully but an error occurred when trying to notify about the completion."); + } + } + + UpdateCurrentStatus(SynchronizationState.Failed, "Synchronization failed.", ex.FlattenMessage(), watch.Elapsed); + throw LogManager.Log(ex, "Error occurred while synchronizing machine data."); + } + finally + { + watch.Stop(); + LogManager.Log($"Synchronization duration: {watch.Elapsed}."); + LastStatus = CurrentStatus; + CreateNewStatus(); + IsSynchronizing = false; + SynchronizationEnded?.Invoke(this, new SynchronizationEndedEventArgs() + { + NewChangedJobs = newChangedJobs, + NewJobRuns = newJobRuns, + NewMachineEvents = newMachineEvents, + }); + } + + _synchTimer.Start(); + } + + private void CreateNewStatus() + { + CurrentStatus = new SynchronizationStatus(); + CurrentStatus.State = SynchronizationState.Pending; + CurrentStatus.StartDateTime = DateTime.Now.Add(Interval); + StatusHistory.Insert(0, CurrentStatus); + } + + private async void ExecuteNewStatus(TimeSpan delay) + { + CurrentStatus = new SynchronizationStatus(); + CurrentStatus.State = SynchronizationState.Pending; + CurrentStatus.StartDateTime = DateTime.Now.Add(delay); + StatusHistory.Insert(0, CurrentStatus); + await Task.Delay(delay); + try + { + if (!_synchronizedOnce) + { + await Synchronize(); + } + } + catch { } + } + + private void UpdateCurrentStatus(SynchronizationState state, String message, String errorReason = null, TimeSpan? duration = null) + { + CurrentStatus.State = state; + CurrentStatus.Message = message; + CurrentStatus.ErrorReason = errorReason; + CurrentStatus.Duration = duration; + CurrentStatusChanged?.Invoke(this, new SynchronizationStatusChangedEventArgs() + { + Status = CurrentStatus, + }); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/IMachineDataSynchronizer.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/IMachineDataSynchronizer.cs new file mode 100644 index 000000000..bfd527a05 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/IMachineDataSynchronizer.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; + +namespace Tango.PPC.Common.Synchronization +{ + public interface IMachineDataSynchronizer + { + event EventHandler SynchronizationStarted; + event EventHandler SynchronizationEnded; + event EventHandler CurrentStatusChanged; + int MaxJobs { get; set; } + int MaxJobRuns { get; set; } + int MaxMachinesEvents { get; set; } + SynchronizationStatus CurrentStatus { get; } + SynchronizationStatus LastStatus { get; } + SynchronizedObservableCollection StatusHistory { get; } + TimeSpan Interval { get; set; } + bool IsEnabled { get; set; } + bool IsSynchronizing { get; } + Task Synchronize(); + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationEndedEventArgs.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationEndedEventArgs.cs new file mode 100644 index 000000000..4b8040e95 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationEndedEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Synchronization +{ + public class SynchronizationEndedEventArgs : EventArgs + { + public int NewChangedJobs { get; set; } + public int NewJobRuns { get; set; } + public int NewMachineEvents { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationState.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationState.cs new file mode 100644 index 000000000..5797f449f --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationState.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Synchronization +{ + public enum SynchronizationState + { + [Description("Pending...")] + Pending, + [Description("Synchronizing...")] + Synchronizing, + [Description("Failed")] + Failed, + [Description("Completed")] + Completed, + [Description("Disabled")] + Disabled + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatus.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatus.cs new file mode 100644 index 000000000..5b1d5d1d2 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatus.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; + +namespace Tango.PPC.Common.Synchronization +{ + public class SynchronizationStatus : ExtendedObject + { + private SynchronizationState _state; + public SynchronizationState State + { + get { return _state; } + set { _state = value; RaisePropertyChangedAuto(); } + } + + private String _message; + public String Message + { + get { return _message; } + set { _message = value; RaisePropertyChangedAuto(); } + } + + private String _errorReason; + public String ErrorReason + { + get { return _errorReason; } + set { _errorReason = value; RaisePropertyChangedAuto(); } + } + + private TimeSpan? _duration; + public TimeSpan? Duration + { + get { return _duration; } + set { _duration = value; RaisePropertyChangedAuto(); } + } + + private DateTime _startDateTime; + public DateTime StartDateTime + { + get { return _startDateTime; } + set { _startDateTime = value; RaisePropertyChangedAuto(); } + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatusChangedEventArgs.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatusChangedEventArgs.cs new file mode 100644 index 000000000..1f0a9a27f --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Synchronization/SynchronizationStatusChangedEventArgs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Synchronization +{ + public class SynchronizationStatusChangedEventArgs : EventArgs + { + public SynchronizationStatus Status { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj b/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj index d38b9c8e9..b781e3c9a 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj @@ -156,6 +156,12 @@ + + + + + + @@ -177,6 +183,8 @@ + + @@ -192,6 +200,7 @@ + @@ -226,8 +235,12 @@ + + + + @@ -406,7 +419,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataRequest.cs new file mode 100644 index 000000000..66fca8e36 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataRequest.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class DownloadMachineDataRequest : WebRequestMessage + { + public bool RequestJobs { get; set; } + public bool RequestJobRuns { get; set; } + public bool RequestMachineEvents { get; set; } + + public int MaxJobs { get; set; } + public int MaxJobRuns { get; set; } + public int MaxMachinesEvents { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataResponse.cs new file mode 100644 index 000000000..5c3f7ba78 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/DownloadMachineDataResponse.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class DownloadMachineDataResponse : WebResponseMessage + { + public List Jobs { get; set; } + public List JobRuns { get; set; } + public List MachineEvents { get; set; } + + public DownloadMachineDataResponse() + { + Jobs = new List(); + JobRuns = new List(); + MachineEvents = new List(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedRequest.cs new file mode 100644 index 000000000..fc135c234 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedRequest.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class NotifyMachineDataDownloadCompletedRequest : WebRequestMessage + { + public List SynchronizedJobs { get; set; } + public List SynchronizedJobRuns { get; set; } + public List SynchronizedMachineEvents { get; set; } + + public NotifyMachineDataDownloadCompletedRequest() + { + SynchronizedJobs = new List(); + SynchronizedJobRuns = new List(); + SynchronizedMachineEvents = new List(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedResponse.cs new file mode 100644 index 000000000..6d5769885 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/NotifyMachineDataDownloadCompletedResponse.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class NotifyMachineDataDownloadCompletedResponse : WebResponseMessage + { + + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClient.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClient.cs index 52c9fdef3..318512fbb 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClient.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClient.cs @@ -30,5 +30,10 @@ namespace Tango.PPC.Common.Web public PPCWebClient(string address, string token) : base(address, token) { } + + public PPCWebClient(PPCWebClient other, TimeSpan requestTimeout) : base(other) + { + RequestTimeout = requestTimeout; + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs index 5520f8b5a..ff972acb2 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs @@ -40,6 +40,15 @@ namespace Tango.PPC.Common.Web } + /// + /// Initializes a new instance of the class. + /// + /// Other instance. + public PPCWebClientBase(PPCWebClientBase cloned) : base(cloned) + { + + } + /// /// Executes the MachineSetup action and returns Tango.PPC.Common.Web.MachineSetupResponse. /// @@ -85,6 +94,33 @@ namespace Tango.PPC.Common.Web return Post("UpdateDB", request); } + /// + /// Executes the UploadMachineData action and returns Tango.PPC.Common.Web.UploadMachineDataResponse. + /// + /// + public Task UploadMachineData(Tango.PPC.Common.Web.UploadMachineDataRequest request) + { + return Post("UploadMachineData", request); + } + + /// + /// Executes the DownloadMachineData action and returns Tango.PPC.Common.Web.DownloadMachineDataResponse. + /// + /// + public Task DownloadMachineData(Tango.PPC.Common.Web.DownloadMachineDataRequest request) + { + return Post("DownloadMachineData", request); + } + + /// + /// Executes the NotifyMachineDataDownloadCompleted action and returns Tango.PPC.Common.Web.NotifyMachineDataDownloadCompletedResponse. + /// + /// + public Task NotifyMachineDataDownloadCompleted(Tango.PPC.Common.Web.NotifyMachineDataDownloadCompletedRequest request) + { + return Post("NotifyMachineDataDownloadCompleted", request); + } + /// /// Executes the GetLatestVersion action and returns Tango.PPC.Common.Web.LatestVersionResponse. /// diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/SynchronizationFailedEntity.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/SynchronizationFailedEntity.cs new file mode 100644 index 000000000..c50044cbe --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/SynchronizationFailedEntity.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.PPC.Common.Web +{ + public class SynchronizationFailedEntity + { + public String Guid { get; set; } + public String Reason { get; set; } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBRequest.cs index f3b4ccb34..c78f6199e 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBRequest.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UpdateDBRequest.cs @@ -10,5 +10,7 @@ namespace Tango.PPC.Common.Web public class UpdateDBRequest : WebRequestMessage { public String SerialNumber { get; set; } + public String ApplicationVersion { get; set; } + public String FirmwareVersion { get; set; } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataRequest.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataRequest.cs new file mode 100644 index 000000000..dc0b05988 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataRequest.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class UploadMachineDataRequest : WebRequestMessage + { + public List Jobs { get; set; } + public List JobRuns { get; set; } + public List MachineEvents { get; set; } + public String ApplicationVersion { get; set; } + public String FirmwareVersion { get; set; } + + public UploadMachineDataRequest() + { + Jobs = new List(); + JobRuns = new List(); + MachineEvents = new List(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataResponse.cs new file mode 100644 index 000000000..e4dda4013 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/UploadMachineDataResponse.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.DTO; +using Tango.Transport.Web; + +namespace Tango.PPC.Common.Web +{ + public class UploadMachineDataResponse : WebResponseMessage + { + public List FailedJobs { get; set; } + public List FailedJobRuns { get; set; } + public List FailedMachineEvents { get; set; } + public String NotifyCompletedToken { get; set; } + + public UploadMachineDataResponse() + { + FailedJobs = new List(); + FailedJobRuns = new List(); + FailedMachineEvents = new List(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/App.config b/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/App.config new file mode 100644 index 000000000..731f6de6c --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/Program.cs b/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/Program.cs new file mode 100644 index 000000000..d77192de2 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/Program.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.SQLExaminer; + +namespace Tango.PPC.SchemaSynchronizer.CLI +{ + class Program + { + static void Main(string[] args) + { + Core.DataSource source = new Core.DataSource(); + source.Address = "localhost\\SQLEXPRESS"; + source.Catalog = "Tango"; + + Core.DataSource target = new Core.DataSource(); + target.Address = "localhost\\SQLPPC"; + target.Catalog = "Tango"; + + ExaminerConfigurationBuilder builder = new ExaminerConfigurationBuilder(ExaminerConfigurationType.Schema); + + builder. + SetSource(source). + SetTarget(target). + Synchronize(); + + var config = builder.Build(); + + ExaminerProcess process = new ExaminerProcess(config, ExaminerProcessType.Schema); + process.Progress += (x, msg) => + { + Console.WriteLine(msg); + }; + var result = process.Execute().Result; + + if (result.ExitCode == ExaminerProcessExitCode.Success) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("Completed!"); + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Failed!"); + } + + Console.ReadLine(); + } + } +} diff --git a/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/Properties/AssemblyInfo.cs b/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..61246489d --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Tango.PPC.SchemaSynchronizer.CLI")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tango.PPC.SchemaSynchronizer.CLI")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f3746f2b-e4ae-498b-9d42-74f95d992460")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/Tango.PPC.SchemaSynchronizer.CLI.csproj b/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/Tango.PPC.SchemaSynchronizer.CLI.csproj new file mode 100644 index 000000000..529815ba2 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.SchemaSynchronizer.CLI/Tango.PPC.SchemaSynchronizer.CLI.csproj @@ -0,0 +1,63 @@ + + + + + Debug + AnyCPU + {F3746F2B-E4AE-498B-9D42-74F95D992460} + Exe + Tango.PPC.SchemaSynchronizer.CLI + Tango.PPC.SchemaSynchronizer.CLI + v4.6.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} + Tango.Core + + + {e1e66ed9-597d-45fa-8048-de90a6930484} + Tango.SQLExaminer + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs index 44b6ffded..70ba4b695 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs @@ -33,6 +33,7 @@ using Tango.Core.Threading; using Tango.PPC.Common.Messages; using Tango.Core.ExtensionMethods; using Tango.PPC.Common.Navigation; +using Tango.PPC.Common.Synchronization; namespace Tango.PPC.UI.PPCApplication { @@ -50,6 +51,7 @@ namespace Tango.PPC.UI.PPCApplication private IEventLogger _eventLogger; private IPPCModuleLoader _moduleLoader; private INotificationProvider _notificationProvider; + private IMachineDataSynchronizer _machineDataSynchronizer; private WatchDogServer _watchdogServer; private ObservablesContext _machineContext; private ActionTimer _screenLockTimer; @@ -146,16 +148,28 @@ namespace Tango.PPC.UI.PPCApplication set { _isScreenLocked = value; RaisePropertyChangedAuto(); } } + /// + /// Gets the firmware version. + /// + public Version FirmwareVersion + { + get + { + return Version.Parse(SettingsManager.Default.GetOrCreate().FirmwareVersion); + } + } + /// /// Initializes a new instance of the class. /// - public DefaultPPCApplicationManager(IMachineProvider machineProvider, IDispatcherProvider dispatcherProvider, IEventLogger eventLogger, IPPCModuleLoader moduleLoader, INotificationProvider notificationProvider) + public DefaultPPCApplicationManager(IMachineProvider machineProvider, IDispatcherProvider dispatcherProvider, IEventLogger eventLogger, IPPCModuleLoader moduleLoader, INotificationProvider notificationProvider, IMachineDataSynchronizer machineDataSynchronizer) { _notificationProvider = notificationProvider; _machineProvider = machineProvider; _dispatcher = dispatcherProvider; _eventLogger = eventLogger; _moduleLoader = moduleLoader; + _machineDataSynchronizer = machineDataSynchronizer; if (!DesignMode) { @@ -248,6 +262,8 @@ namespace Tango.PPC.UI.PPCApplication LogManager.Log("Loading machine from database..."); _machineContext = ObservablesContext.CreateDefault(); _machine = new MachineBuilder(_machineContext).SetFirst().WithVersion().WithSettings().WithOrganization().WithConfiguration().WithSpools().WithCats().Build(); + + } initialized = true; @@ -349,6 +365,9 @@ namespace Tango.PPC.UI.PPCApplication LogManager.Log("Initializing Machine Provider..."); _machineProvider.Init(_machine, _machineContext); + LogManager.Log("Starting Machine Data Synchronizer..."); + _machineDataSynchronizer.IsEnabled = true; + LogManager.Log("Applications initialization completed!"); LogManager.Log("Checking for un-notified PPC view models..."); diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs b/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs index 60bce4e20..49157a998 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/ViewModelLocator.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Windows; using Tango.Core.DI; using Tango.Integration.ExternalBridge; @@ -21,6 +22,7 @@ using Tango.PPC.Common.OS; using Tango.PPC.Common.Printing; using Tango.PPC.Common.RemoteAssistance; using Tango.PPC.Common.Storage; +using Tango.PPC.Common.Synchronization; using Tango.PPC.Common.Threading; using Tango.PPC.Common.UpdatePackages; using Tango.PPC.Common.UWF; @@ -75,8 +77,17 @@ namespace Tango.PPC.UI TangoIOC.Default.Unregister(); TangoIOC.Default.Unregister(); TangoIOC.Default.Unregister(); + TangoIOC.Default.Unregister(); + + if (App.StartupArgs.Contains("-webDebug")) + { + TangoIOC.Default.Register(new PPCWebClient("http://localhost:1111", null)); + } + else + { + TangoIOC.Default.Register(new PPCWebClient()); + } - TangoIOC.Default.Register(new PPCWebClient()); TangoIOC.Default.Register(new DefaultDispatcherProvider(Application.Current.Dispatcher)); TangoIOC.Default.Register(); TangoIOC.Default.Register(); @@ -84,6 +95,7 @@ namespace Tango.PPC.UI TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); + TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); TangoIOC.Default.Register(); diff --git a/Software/Visual_Studio/Tango.BL/Builders/EntityCollectionBuilderBase.cs b/Software/Visual_Studio/Tango.BL/Builders/EntityCollectionBuilderBase.cs index a0cd24511..d4d65c16d 100644 --- a/Software/Visual_Studio/Tango.BL/Builders/EntityCollectionBuilderBase.cs +++ b/Software/Visual_Studio/Tango.BL/Builders/EntityCollectionBuilderBase.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -13,6 +14,7 @@ namespace Tango.BL.Builders private List> _steps; private List, IQueryable>>> _querySteps; private bool _entity_set; + private Func, IQueryable> _appendQuery; protected IEnumerable Entities { get; set; } @@ -38,6 +40,11 @@ namespace Tango.BL.Builders query = queryStep.Value(query); } + if (_appendQuery != null) + { + query = _appendQuery(query); + } + Entities = query.ToList(); }); @@ -58,6 +65,11 @@ namespace Tango.BL.Builders query = queryStep.Value(query); } + if (_appendQuery != null) + { + query = _appendQuery(query); + } + Entities = query.ToList(); }); @@ -71,6 +83,12 @@ namespace Tango.BL.Builders return query; } + public TBuilder Query(Func, IQueryable> query) + { + _appendQuery = query; + return this as TBuilder; + } + protected void CommitSteps() { foreach (var step in _steps.ToList().DistinctBy(x => x.Key).OrderBy(x => x.Key)) @@ -104,6 +122,18 @@ namespace Tango.BL.Builders return Entities.ToSynchronizedObservableCollection(); } + public List BuildList() + { + if (!_entity_set) + { + throw new InvalidOperationException("Could not build entity. Entity was not set."); + } + + CommitSteps(); + + return Entities.ToList(); + } + public Task> BuildAsync() { return Task.Factory.StartNew>(() => @@ -111,5 +141,13 @@ namespace Tango.BL.Builders return Build(); }); } + + public Task> BuildListAsync() + { + return Task.Factory.StartNew>(() => + { + return BuildList(); + }); + } } } diff --git a/Software/Visual_Studio/Tango.BL/DTO/JobDTO.cs b/Software/Visual_Studio/Tango.BL/DTO/JobDTO.cs index 137d7b4b3..7ee6c9bb0 100644 --- a/Software/Visual_Studio/Tango.BL/DTO/JobDTO.cs +++ b/Software/Visual_Studio/Tango.BL/DTO/JobDTO.cs @@ -9,6 +9,11 @@ namespace Tango.BL.DTO { public class JobDTO : JobDTOBase { + public List Segments { get; set; } + public JobDTO() + { + Segments = new List(); + } } } diff --git a/Software/Visual_Studio/Tango.BL/DTO/JobDTOBase.cs b/Software/Visual_Studio/Tango.BL/DTO/JobDTOBase.cs index f2842c4fd..c3c418f1f 100644 --- a/Software/Visual_Studio/Tango.BL/DTO/JobDTOBase.cs +++ b/Software/Visual_Studio/Tango.BL/DTO/JobDTOBase.cs @@ -274,5 +274,13 @@ namespace Tango.BL.DTO get; set; } + /// + /// is synchronized + /// + public Boolean IsSynchronized + { + get; set; + } + } } diff --git a/Software/Visual_Studio/Tango.BL/DTO/JobRunDTOBase.cs b/Software/Visual_Studio/Tango.BL/DTO/JobRunDTOBase.cs index 9726f16a7..b47ce3ea9 100644 --- a/Software/Visual_Studio/Tango.BL/DTO/JobRunDTOBase.cs +++ b/Software/Visual_Studio/Tango.BL/DTO/JobRunDTOBase.cs @@ -82,5 +82,13 @@ namespace Tango.BL.DTO get; set; } + /// + /// is synchronized + /// + public Boolean IsSynchronized + { + get; set; + } + } } diff --git a/Software/Visual_Studio/Tango.BL/DTO/MachinesEventDTOBase.cs b/Software/Visual_Studio/Tango.BL/DTO/MachinesEventDTOBase.cs index 8a044f7e8..e990f2a31 100644 --- a/Software/Visual_Studio/Tango.BL/DTO/MachinesEventDTOBase.cs +++ b/Software/Visual_Studio/Tango.BL/DTO/MachinesEventDTOBase.cs @@ -69,5 +69,13 @@ namespace Tango.BL.DTO get; set; } + /// + /// is synchronized + /// + public Boolean IsSynchronized + { + get; set; + } + } } diff --git a/Software/Visual_Studio/Tango.BL/DTO/SegmentDTO.cs b/Software/Visual_Studio/Tango.BL/DTO/SegmentDTO.cs index 1db99be23..09f581db2 100644 --- a/Software/Visual_Studio/Tango.BL/DTO/SegmentDTO.cs +++ b/Software/Visual_Studio/Tango.BL/DTO/SegmentDTO.cs @@ -9,6 +9,11 @@ namespace Tango.BL.DTO { public class SegmentDTO : SegmentDTOBase { + public List BrushStops { get; set; } + public SegmentDTO() + { + BrushStops = new List(); + } } } diff --git a/Software/Visual_Studio/Tango.BL/Entities/Job.cs b/Software/Visual_Studio/Tango.BL/Entities/Job.cs index 19f374951..d2a416271 100644 --- a/Software/Visual_Studio/Tango.BL/Entities/Job.cs +++ b/Software/Visual_Studio/Tango.BL/Entities/Job.cs @@ -335,6 +335,7 @@ namespace Tango.BL.Entities cloned.Name = Name + " - Copy"; cloned.CreationDate = DateTime.UtcNow; + cloned.IsSynchronized = false; cloned.LastRun = null; cloned.ColorSpace = ColorSpace; cloned.Customer = Customer; diff --git a/Software/Visual_Studio/Tango.BL/Entities/JobBase.cs b/Software/Visual_Studio/Tango.BL/Entities/JobBase.cs index b356ecbfe..599e379a8 100644 --- a/Software/Visual_Studio/Tango.BL/Entities/JobBase.cs +++ b/Software/Visual_Studio/Tango.BL/Entities/JobBase.cs @@ -81,6 +81,8 @@ namespace Tango.BL.Entities public event EventHandler LengthPercentageFactorChanged; + public event EventHandler IsSynchronizedChanged; + public event EventHandler ColorCatalogChanged; public event EventHandler ColorSpaceChanged; @@ -993,6 +995,33 @@ namespace Tango.BL.Entities } } + protected Boolean _issynchronized; + + /// + /// Gets or sets the jobbase is synchronized. + /// + + [Column("IS_SYNCHRONIZED")] + + public Boolean IsSynchronized + { + get + { + return _issynchronized; + } + + set + { + if (_issynchronized != value) + { + _issynchronized = value; + + OnIsSynchronizedChanged(value); + + } + } + } + protected ColorCatalog _colorcatalog; /// @@ -1499,6 +1528,15 @@ namespace Tango.BL.Entities RaisePropertyChanged(nameof(LengthPercentageFactor)); } + /// + /// Called when the IsSynchronized has changed. + /// + protected virtual void OnIsSynchronizedChanged(Boolean issynchronized) + { + IsSynchronizedChanged?.Invoke(this, issynchronized); + RaisePropertyChanged(nameof(IsSynchronized)); + } + /// /// Called when the ColorCatalog has changed. /// diff --git a/Software/Visual_Studio/Tango.BL/Entities/JobRunBase.cs b/Software/Visual_Studio/Tango.BL/Entities/JobRunBase.cs index 16351179f..c90aa5eab 100644 --- a/Software/Visual_Studio/Tango.BL/Entities/JobRunBase.cs +++ b/Software/Visual_Studio/Tango.BL/Entities/JobRunBase.cs @@ -41,6 +41,8 @@ namespace Tango.BL.Entities public event EventHandler FailedMessageChanged; + public event EventHandler IsSynchronizedChanged; + protected String _machineguid; /// @@ -228,6 +230,33 @@ namespace Tango.BL.Entities } } + protected Boolean _issynchronized; + + /// + /// Gets or sets the jobrunbase is synchronized. + /// + + [Column("IS_SYNCHRONIZED")] + + public Boolean IsSynchronized + { + get + { + return _issynchronized; + } + + set + { + if (_issynchronized != value) + { + _issynchronized = value; + + OnIsSynchronizedChanged(value); + + } + } + } + /// /// Called when the StartDate has changed. /// @@ -273,6 +302,15 @@ namespace Tango.BL.Entities RaisePropertyChanged(nameof(FailedMessage)); } + /// + /// Called when the IsSynchronized has changed. + /// + protected virtual void OnIsSynchronizedChanged(Boolean issynchronized) + { + IsSynchronizedChanged?.Invoke(this, issynchronized); + RaisePropertyChanged(nameof(IsSynchronized)); + } + /// /// Initializes a new instance of the class. /// diff --git a/Software/Visual_Studio/Tango.BL/Entities/MachinesEventBase.cs b/Software/Visual_Studio/Tango.BL/Entities/MachinesEventBase.cs index 5091267e8..0a3377dce 100644 --- a/Software/Visual_Studio/Tango.BL/Entities/MachinesEventBase.cs +++ b/Software/Visual_Studio/Tango.BL/Entities/MachinesEventBase.cs @@ -32,6 +32,8 @@ namespace Tango.BL.Entities public event EventHandler DescriptionChanged; + public event EventHandler IsSynchronizedChanged; + public event EventHandler EventTypeChanged; public event EventHandler MachineChanged; @@ -197,6 +199,33 @@ namespace Tango.BL.Entities } } + protected Boolean _issynchronized; + + /// + /// Gets or sets the machineseventbase is synchronized. + /// + + [Column("IS_SYNCHRONIZED")] + + public Boolean IsSynchronized + { + get + { + return _issynchronized; + } + + set + { + if (_issynchronized != value) + { + _issynchronized = value; + + OnIsSynchronizedChanged(value); + + } + } + } + protected EventType _eventtype; /// @@ -320,6 +349,15 @@ namespace Tango.BL.Entities RaisePropertyChanged(nameof(Description)); } + /// + /// Called when the IsSynchronized has changed. + /// + protected virtual void OnIsSynchronizedChanged(Boolean issynchronized) + { + IsSynchronizedChanged?.Invoke(this, issynchronized); + RaisePropertyChanged(nameof(IsSynchronized)); + } + /// /// Called when the EventType has changed. /// diff --git a/Software/Visual_Studio/Tango.BL/Entities/TangoUpdate.cs b/Software/Visual_Studio/Tango.BL/Entities/TangoUpdate.cs index 5c254a6a9..4ab67b7c5 100644 --- a/Software/Visual_Studio/Tango.BL/Entities/TangoUpdate.cs +++ b/Software/Visual_Studio/Tango.BL/Entities/TangoUpdate.cs @@ -65,6 +65,19 @@ namespace Tango.BL.Entities } } + [NotMapped] + [JsonIgnore] + public bool IsSynchronization + { + get + { + return + UpdateStatus == TangoUpdateStatuses.SynchronizationStarted || + UpdateStatus == TangoUpdateStatuses.SynchronizationCompleted || + UpdateStatus == TangoUpdateStatuses.SynchronizationFailed; + } + } + [NotMapped] [JsonIgnore] public bool IsStarted @@ -74,7 +87,8 @@ namespace Tango.BL.Entities return UpdateStatus == TangoUpdateStatuses.SetupStarted || UpdateStatus == TangoUpdateStatuses.UpdateStarted || - UpdateStatus == TangoUpdateStatuses.DatabaseStarted; + UpdateStatus == TangoUpdateStatuses.DatabaseStarted || + UpdateStatus == TangoUpdateStatuses.SynchronizationStarted; } } @@ -87,7 +101,8 @@ namespace Tango.BL.Entities return UpdateStatus == TangoUpdateStatuses.SetupCompleted || UpdateStatus == TangoUpdateStatuses.UpdateCompleted || - UpdateStatus == TangoUpdateStatuses.DatabaseCompleted; + UpdateStatus == TangoUpdateStatuses.DatabaseCompleted || + UpdateStatus == TangoUpdateStatuses.SynchronizationCompleted; } } @@ -100,7 +115,8 @@ namespace Tango.BL.Entities return UpdateStatus == TangoUpdateStatuses.SetupFailed || UpdateStatus == TangoUpdateStatuses.UpdateFailed || - UpdateStatus == TangoUpdateStatuses.DatabaseFailed; + UpdateStatus == TangoUpdateStatuses.DatabaseFailed || + UpdateStatus == TangoUpdateStatuses.SynchronizationFailed; } } diff --git a/Software/Visual_Studio/Tango.BL/Enumerations/TangoUpdateStatuses.cs b/Software/Visual_Studio/Tango.BL/Enumerations/TangoUpdateStatuses.cs index 30d324317..855d8b29b 100644 --- a/Software/Visual_Studio/Tango.BL/Enumerations/TangoUpdateStatuses.cs +++ b/Software/Visual_Studio/Tango.BL/Enumerations/TangoUpdateStatuses.cs @@ -16,7 +16,7 @@ namespace Tango.BL.Enumerations [Description("Setup failed")] SetupFailed = 2, - [Description("Software updated started but did not complete")] + [Description("Software update started but did not complete")] UpdateStarted = 100, [Description("Software update completed successfully")] UpdateCompleted = 101, @@ -29,5 +29,12 @@ namespace Tango.BL.Enumerations DatabaseCompleted = 201, [Description("Database update failed")] DatabaseFailed = 202, + + [Description("Synchronization started but did not complete")] + SynchronizationStarted = 300, + [Description("Synchronization completed successfully")] + SynchronizationCompleted = 301, + [Description("Synchronization failed")] + SynchronizationFailed = 302, } } diff --git a/Software/Visual_Studio/Tango.BL/IObservableEntity.cs b/Software/Visual_Studio/Tango.BL/IObservableEntity.cs index 244486add..96a150b4a 100644 --- a/Software/Visual_Studio/Tango.BL/IObservableEntity.cs +++ b/Software/Visual_Studio/Tango.BL/IObservableEntity.cs @@ -90,5 +90,10 @@ namespace Tango.BL /// /// The context. void Delete(ObservablesContext context); + + /// + /// Called when before entity is saved by the context. + /// + void OnBeforeSave(); } } diff --git a/Software/Visual_Studio/Tango.BL/ObservableEntity.cs b/Software/Visual_Studio/Tango.BL/ObservableEntity.cs index 982b853f9..e9bb711da 100644 --- a/Software/Visual_Studio/Tango.BL/ObservableEntity.cs +++ b/Software/Visual_Studio/Tango.BL/ObservableEntity.cs @@ -620,6 +620,18 @@ namespace Tango.BL #endregion + #region Virtual Methods + + /// + /// Called when before entity is saved by the context. + /// + public virtual void OnBeforeSave() + { + + } + + #endregion + #region INotify Property Changed /// diff --git a/Software/Visual_Studio/Tango.BL/ObservablesContextExtension.cs b/Software/Visual_Studio/Tango.BL/ObservablesContextExtension.cs index b7559cdc3..263574f68 100644 --- a/Software/Visual_Studio/Tango.BL/ObservablesContextExtension.cs +++ b/Software/Visual_Studio/Tango.BL/ObservablesContextExtension.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; +using System.Data.Entity.Core.Objects; using System.Data.Entity.Infrastructure; using System.Data.SQLite; using System.IO; @@ -35,7 +36,7 @@ namespace Tango.BL /// public ObservablesContext() { - + } /// @@ -102,6 +103,15 @@ namespace Tango.BL return CreateDefault(address, "Tango", DataSourceType.SQLServer); } + /// + /// Gets the inner object context. + /// + /// + private ObjectContext GetObjectContext() + { + return ((IObjectContextAdapter)this).ObjectContext; + } + /// /// Saves all changes made in this context to the underlying database. /// @@ -110,6 +120,14 @@ namespace Tango.BL /// public override int SaveChanges() { + foreach (var entity in ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified).ToList()) + { + if (entity is IObservableEntity && entity != null) + { + (entity as IObservableEntity).OnBeforeSave(); + } + } + var result = base.SaveChanges(); RaisePendingNotifications(); return result; @@ -129,6 +147,14 @@ namespace Tango.BL /// public override Task SaveChangesAsync(CancellationToken cancellationToken) { + foreach (var entity in ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified).ToList().Select(x => x.Entity).ToList()) + { + if (entity is IObservableEntity && entity != null) + { + (entity as IObservableEntity).OnBeforeSave(); + } + } + var result = base.SaveChangesAsync(cancellationToken); RaisePendingNotifications(); return result; @@ -168,9 +194,6 @@ namespace Tango.BL { IObservableEntity modified = entityEntry.Entity as IObservableEntity; - //Good chance to update "LAST_UPDATED" field! - modified.LastUpdated = DateTime.UtcNow; - foreach (var toNotify in ObservableEntitiesContainer.RegisteredEntities.ToList().Where(x => x.Guid == modified.Guid).ToList()) { _pending_notifications.Add(new ObservableModifiedEventArgs(this, modified, toNotify)); diff --git a/Software/Visual_Studio/Tango.CodeGeneration/Templates/TangoWebClientCodeFile.cshtml b/Software/Visual_Studio/Tango.CodeGeneration/Templates/TangoWebClientCodeFile.cshtml index 7d9ebc075..3918ce928 100644 --- a/Software/Visual_Studio/Tango.CodeGeneration/Templates/TangoWebClientCodeFile.cshtml +++ b/Software/Visual_Studio/Tango.CodeGeneration/Templates/TangoWebClientCodeFile.cshtml @@ -42,6 +42,15 @@ namespace @(model.Namespace) } + /// + /// Initializes a new instance of the class. + /// + /// Other instance. + public @(model.Name)(@(model.Name) cloned) : base(cloned) + { + + } + @foreach (var action in model.Actions) {
diff --git a/Software/Visual_Studio/Tango.Core/ExtensionMethods/ExceptionExtensions.cs b/Software/Visual_Studio/Tango.Core/ExtensionMethods/ExceptionExtensions.cs index af4fc39b5..7b87245b7 100644 --- a/Software/Visual_Studio/Tango.Core/ExtensionMethods/ExceptionExtensions.cs +++ b/Software/Visual_Studio/Tango.Core/ExtensionMethods/ExceptionExtensions.cs @@ -30,6 +30,23 @@ public static class ExceptionExtensions return stringBuilder.ToString(); } + /// + /// Gets the first exception if this is an aggregated exception. + /// + /// The exception. + /// + public static Exception GetFirstIfAggregate(this Exception exception) + { + var ex = exception as AggregateException; + + if (ex != null && ex.InnerExceptions.Count > 0) + { + return ex.InnerExceptions.First(); + } + + return exception; + } + /// /// Flattens the exception message in case it is an aggregated exception. /// diff --git a/Software/Visual_Studio/Tango.DAL.Remote/DB/JOB.cs b/Software/Visual_Studio/Tango.DAL.Remote/DB/JOB.cs index 17ec544c0..923cceacf 100644 --- a/Software/Visual_Studio/Tango.DAL.Remote/DB/JOB.cs +++ b/Software/Visual_Studio/Tango.DAL.Remote/DB/JOB.cs @@ -56,6 +56,7 @@ namespace Tango.DAL.Remote.DB public Nullable SAMPLE_DYE_APPROVE_DATE { get; set; } public int EDITING_STATE { get; set; } public double LENGTH_PERCENTAGE_FACTOR { get; set; } + public bool IS_SYNCHRONIZED { get; set; } public virtual COLOR_CATALOGS COLOR_CATALOGS { get; set; } public virtual COLOR_SPACES COLOR_SPACES { get; set; } diff --git a/Software/Visual_Studio/Tango.DAL.Remote/DB/JOB_RUNS.cs b/Software/Visual_Studio/Tango.DAL.Remote/DB/JOB_RUNS.cs index 24f61f03c..186c82727 100644 --- a/Software/Visual_Studio/Tango.DAL.Remote/DB/JOB_RUNS.cs +++ b/Software/Visual_Studio/Tango.DAL.Remote/DB/JOB_RUNS.cs @@ -24,5 +24,6 @@ namespace Tango.DAL.Remote.DB public int STATUS { get; set; } public double END_POSITION { get; set; } public string FAILED_MESSAGE { get; set; } + public bool IS_SYNCHRONIZED { get; set; } } } diff --git a/Software/Visual_Studio/Tango.DAL.Remote/DB/MACHINES_EVENTS.cs b/Software/Visual_Studio/Tango.DAL.Remote/DB/MACHINES_EVENTS.cs index d83f123a2..c555a1ce3 100644 --- a/Software/Visual_Studio/Tango.DAL.Remote/DB/MACHINES_EVENTS.cs +++ b/Software/Visual_Studio/Tango.DAL.Remote/DB/MACHINES_EVENTS.cs @@ -23,6 +23,7 @@ namespace Tango.DAL.Remote.DB public string USER_GUID { get; set; } public System.DateTime DATE_TIME { get; set; } public string DESCRIPTION { get; set; } + public bool IS_SYNCHRONIZED { get; set; } public virtual EVENT_TYPES EVENT_TYPES { get; set; } public virtual MACHINE MACHINE { get; set; } diff --git a/Software/Visual_Studio/Tango.DAL.Remote/DB/RemoteADO.edmx b/Software/Visual_Studio/Tango.DAL.Remote/DB/RemoteADO.edmx index 8c8b66a5c..1a27648e2 100644 --- a/Software/Visual_Studio/Tango.DAL.Remote/DB/RemoteADO.edmx +++ b/Software/Visual_Studio/Tango.DAL.Remote/DB/RemoteADO.edmx @@ -593,6 +593,7 @@ + @@ -634,6 +635,7 @@ + @@ -745,6 +747,7 @@ + @@ -3648,6 +3651,7 @@ + @@ -3689,6 +3693,7 @@ + @@ -3828,6 +3833,7 @@ + @@ -5924,6 +5930,7 @@ + @@ -5940,6 +5947,7 @@ + @@ -6085,6 +6093,7 @@ + diff --git a/Software/Visual_Studio/Tango.DAL.Remote/DB/RemoteADO.edmx.diagram b/Software/Visual_Studio/Tango.DAL.Remote/DB/RemoteADO.edmx.diagram index 2feb68642..a3618fb5f 100644 --- a/Software/Visual_Studio/Tango.DAL.Remote/DB/RemoteADO.edmx.diagram +++ b/Software/Visual_Studio/Tango.DAL.Remote/DB/RemoteADO.edmx.diagram @@ -5,80 +5,80 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/Tango.Settings/SettingsManager.cs b/Software/Visual_Studio/Tango.Settings/SettingsManager.cs index 88a3f59e1..d106ebaca 100644 --- a/Software/Visual_Studio/Tango.Settings/SettingsManager.cs +++ b/Software/Visual_Studio/Tango.Settings/SettingsManager.cs @@ -93,7 +93,6 @@ namespace Tango.Settings { try { - _logManager.Log("Retrieving settings for " + typeof(T).Name); EnsureLoaded(); } catch (Exception ex) diff --git a/Software/Visual_Studio/Tango.Transport/Web/WebTransportClient.cs b/Software/Visual_Studio/Tango.Transport/Web/WebTransportClient.cs index ed2e69468..e838a6a05 100644 --- a/Software/Visual_Studio/Tango.Transport/Web/WebTransportClient.cs +++ b/Software/Visual_Studio/Tango.Transport/Web/WebTransportClient.cs @@ -21,6 +21,12 @@ namespace Tango.Transport.Web public string AuthenticationToken { get; set; } + public TimeSpan RequestTimeout + { + get { return _httpClient.Timeout; } + set { _httpClient.Timeout = value; } + } + static WebTransportClient() { _settings = new JsonSerializerSettings() @@ -109,6 +115,7 @@ namespace Tango.Transport.Web if (type != null) { exception = Activator.CreateInstance(type, new object[] { exceptionMessage + "\n" + stackTrace }) as Exception; + } else { diff --git a/Software/Visual_Studio/Tango.Web/Controllers/TangoController.cs b/Software/Visual_Studio/Tango.Web/Controllers/TangoController.cs index d82fcb5f2..3fd3d469e 100644 --- a/Software/Visual_Studio/Tango.Web/Controllers/TangoController.cs +++ b/Software/Visual_Studio/Tango.Web/Controllers/TangoController.cs @@ -70,7 +70,7 @@ namespace Tango.Web.Controllers code = HttpStatusCode.Unauthorized; } - var httpException = new HttpResponseException(Request.CreateErrorResponse(code, ex.FlattenMessage())); + var httpException = new HttpResponseException(Request.CreateErrorResponse(code, ex.FlattenMessage(), ex)); #if DEBUG throw httpException; diff --git a/Software/Visual_Studio/Tango.Web/TangoWebClient.cs b/Software/Visual_Studio/Tango.Web/TangoWebClient.cs index 1d2f9fc8e..7c3c6ad2a 100644 --- a/Software/Visual_Studio/Tango.Web/TangoWebClient.cs +++ b/Software/Visual_Studio/Tango.Web/TangoWebClient.cs @@ -37,6 +37,12 @@ namespace Tango.Web public WebToken WebToken { get; private set; } public bool IsAuthenticated { get; private set; } + public TimeSpan RequestTimeout + { + get { return _client.RequestTimeout; } + set { _client.RequestTimeout = value; } + } + public TangoWebClient(DeploymentSlot environment, String controller) { _client = new WebTransportClient(); @@ -57,6 +63,24 @@ namespace Tango.Web Token = token; } + public TangoWebClient(TangoWebClient cloned) + { + _client = new WebTransportClient(); + Environment = cloned.Environment; + Address = cloned.Address; + Controller = cloned.Controller; + IsAuthenticated = cloned.IsAuthenticated; + RequestTimeout = cloned.RequestTimeout; + WebToken = cloned.WebToken; + _lastLoginRequest = cloned._lastLoginRequest; + Token = cloned.Token; + } + + public T Clone() where T : TangoWebClient + { + return Activator.CreateInstance(typeof(T), new object[] { this }) as T; + } + public async Task Login(TLoginRequest request) { var response = await _client.PostJson(GetActionAddress("Login"), request); @@ -85,7 +109,7 @@ namespace Tango.Web var response = await _client.PostJson(GetActionAddress(action), request); return response; } - catch (TokenExpiredException) + catch (TokenExpiredException ex) { try { @@ -98,7 +122,7 @@ namespace Tango.Web throw; } } - catch + catch (Exception ex) { throw; } diff --git a/Software/Visual_Studio/Tango.sln b/Software/Visual_Studio/Tango.sln index 8dc160543..b089332d6 100644 --- a/Software/Visual_Studio/Tango.sln +++ b/Software/Visual_Studio/Tango.sln @@ -325,6 +325,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.PPC.Packages.SamplePr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.PPC.Packages.SamplePostPackage", "PPC\UpdatePackages\Tango.PPC.Packages.SamplePostPackage\Tango.PPC.Packages.SamplePostPackage.csproj", "{DA391B02-AE28-4EA1-A80F-D0F4C8029FFA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.PPC.SchemaSynchronizer.CLI", "PPC\Tango.PPC.SchemaSynchronizer.CLI\Tango.PPC.SchemaSynchronizer.CLI.csproj", "{F3746F2B-E4AE-498B-9D42-74F95D992460}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AppVeyor|Any CPU = AppVeyor|Any CPU @@ -5777,6 +5779,46 @@ Global {DA391B02-AE28-4EA1-A80F-D0F4C8029FFA}.Release|x64.Build.0 = Release|Any CPU {DA391B02-AE28-4EA1-A80F-D0F4C8029FFA}.Release|x86.ActiveCfg = Release|Any CPU {DA391B02-AE28-4EA1-A80F-D0F4C8029FFA}.Release|x86.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|Any CPU.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|ARM.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|ARM.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|ARM64.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|x64.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|x64.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|x86.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.AppVeyor|x86.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|ARM.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|ARM.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|ARM64.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|x64.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|x64.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|x86.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Debug|x86.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|ARM.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|x64.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.DefaultBuild|x86.Build.0 = Debug|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|Any CPU.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|ARM.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|ARM.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|ARM64.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|ARM64.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|x64.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|x64.Build.0 = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|x86.ActiveCfg = Release|Any CPU + {F3746F2B-E4AE-498B-9D42-74F95D992460}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5884,6 +5926,7 @@ Global {D74893F2-9E39-4C72-BDD4-937404E1FC37} = {5F6BBAA8-EAD0-4B18-97E5-55B4F56DD760} {2CD12594-3522-4658-A65F-190EE58B6AFA} = {E728CBD9-1AF4-4814-A218-E4BD26E7EDEA} {DA391B02-AE28-4EA1-A80F-D0F4C8029FFA} = {E728CBD9-1AF4-4814-A218-E4BD26E7EDEA} + {F3746F2B-E4AE-498B-9D42-74F95D992460} = {C81ED1A3-D18C-4D80-A8F5-061994A14A60} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution BuildVersion_UseGlobalSettings = False diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs index 378fc3eea..da5ce16f7 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs +++ b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs @@ -285,7 +285,16 @@ namespace Tango.MachineService.Controllers [HttpPost] public LoginResponse Login(LoginRequest request) { - var authResult = _ad_manager.ValidateUserCredentials(request.Email, request.Password); + AuthenticationResult authResult = null; + + try + { + authResult = _ad_manager.ValidateUserCredentials(request.Email, request.Password); + } + catch (Exception ex) + { + throw new AuthenticationException(ex.FlattenMessage()); + } if (!_ad_manager.CanUserAccessCurrentEnvironment(request.Email)) { diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs index 0d9a2993b..b2a1da980 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs +++ b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs @@ -398,12 +398,9 @@ namespace Tango.MachineService.Controllers Type = DataSourceType.SQLServer, }; - var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == machine.MachineVersionGuid); - var latest_machine_version = db.TangoVersions.Where(x => x.MachineVersionGuid == machine_version.Guid).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); - TangoUpdate tangoUpdate = new TangoUpdate(); - tangoUpdate.ApplicationVersion = latest_machine_version.Version; - tangoUpdate.FirmwareVersion = latest_machine_version.FirmwareVersion; + tangoUpdate.ApplicationVersion = request.ApplicationVersion; + tangoUpdate.FirmwareVersion = request.FirmwareVersion; tangoUpdate.MachineGuid = machine.Guid; tangoUpdate.StartDate = DateTime.UtcNow; tangoUpdate.UpdateStatus = TangoUpdateStatuses.DatabaseStarted; @@ -423,6 +420,276 @@ namespace Tango.MachineService.Controllers #endregion + #region Synchronization + + [HttpPost] + [JwtTokenFilter] + public UploadMachineDataResponse UploadMachineData(UploadMachineDataRequest request) + { + UploadMachineDataResponse response = new UploadMachineDataResponse(); + response.NotifyCompletedToken = Guid.NewGuid().ToString(); + + using (ObservablesContext db = ObservablesContextHelper.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.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, + }); + } + + User machineUser = null; + + try + { + using (ObservablesContext db = ObservablesContextHelper.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."); + } + + machineUser = db.Users.Include(x => x.Contact).SingleOrDefault(x => x.Contact.FirstName == machine.Name); + + if (machineUser == null) + { + //No machine user then create one. + machineUser = new User(); + machineUser.Email = machine.SerialNumber + "@twine-s.com"; + machineUser.Password = machine.SerialNumber; + machineUser.OrganizationGuid = machine.OrganizationGuid; + machineUser.Contact = new Contact(); + machineUser.Contact.Email = machineUser.Email; + machineUser.Contact.FirstName = machine.Name; + machineUser.Contact.LastName = machine.Name; + machineUser.Contact.FullName = machine.Name; + machineUser.Address = new Address(); + + db.Users.Add(machineUser); + db.SaveChanges(); + } + } + + //Insert/Replace Jobs. + foreach (var dto in request.Jobs) + { + using (ObservablesContext db = ObservablesContextHelper.CreateContext()) + { + try + { + var job = dto.ToObservable(); + + job.ID = 0; + job.UserGuid = machineUser.Guid; + 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) + { + existingJob.Delete(db); + db.Jobs.Add(job); + db.SaveChanges(); + } + } + catch (Exception ex) + { + response.FailedJobs.Add(new SynchronizationFailedEntity() + { + Guid = dto.Guid, + Reason = ex.FlattenMessage(), + }); + } + } + } + + //Insert JobRuns. + foreach (var dto in request.JobRuns) + { + using (ObservablesContext db = ObservablesContextHelper.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 MachineEvents. + foreach (var dto in request.MachineEvents) + { + using (ObservablesContext db = ObservablesContextHelper.CreateContext()) + { + try + { + var ev = dto.ToObservable(); + ev.ID = 0; + ev.UserGuid = machineUser.Guid; + 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 = ex.FlattenException(), + Token = response.NotifyCompletedToken, + }); + throw ex; + } + + return response; + } + + [HttpPost] + [JwtTokenFilter] + public DownloadMachineDataResponse DownloadMachineData(DownloadMachineDataRequest request) + { + DownloadMachineDataResponse response = new DownloadMachineDataResponse(); + + using (ObservablesContext db = ObservablesContextHelper.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); + } + } + } + + return response; + } + + [HttpPost] + [JwtTokenFilter] + public NotifyMachineDataDownloadCompletedResponse NotifyMachineDataDownloadCompleted(NotifyMachineDataDownloadCompletedRequest request) + { + var response = new NotifyMachineDataDownloadCompletedResponse(); + + using (ObservablesContext db = ObservablesContextHelper.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 + "'"))});"); + } + } + + return response; + } + + #endregion + #region Version Upload [HttpPost] diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Web.config b/Software/Visual_Studio/Web/Tango.MachineService/Web.config index 97a0d511f..658439cc0 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Web.config +++ b/Software/Visual_Studio/Web/Tango.MachineService/Web.config @@ -25,7 +25,7 @@ - + @@ -62,6 +62,11 @@ + + + + + -- cgit v1.3.1 From b9c104e259ca24d2ae7ca82387209779fefddd34 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Sun, 19 Jan 2020 00:30:23 +0200 Subject: Implemented machine studio connection via access token WORKING!. Implemented machine studio login method selection. --- ...go.MachineStudio.Storage_yjpbed13_wpftmp.csproj | 154 ++++++++++++++++++ .../Authentication/IAuthenticationProvider.cs | 2 +- .../MachineStudioSettings.cs | 6 + .../Tango.MachineStudio.Common.csproj | 3 +- .../Tango.MachineStudio.Common/Web/LoginMethod.cs | 17 ++ .../Tango.MachineStudio.Common/Web/LoginRequest.cs | 1 + .../DefaultAuthenticationProvider.cs | 3 +- .../Images/active_directory.png | Bin 0 -> 15300 bytes .../Tango.MachineStudio.UI/Images/login.png | Bin 0 -> 6401 bytes .../Images/machinestudio_login.png | Bin 0 -> 21788 bytes .../Tango.MachineStudio.UI.csproj | 5 +- .../ViewModels/LoginViewVM.cs | 29 +++- .../Tango.MachineStudio.UI/Views/LoginView.xaml | 76 ++++++--- .../Tango.MachineStudio.UI/Views/MainView.xaml | 2 +- .../Tango.BL/ObservablesContextExtension.cs | 24 ++- .../Tango.Web/SQLServer/SQLServerManager.cs | 1 + .../Controllers/MachineStudioController.cs | 181 ++++++++++++--------- 17 files changed, 395 insertions(+), 109 deletions(-) create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Storage/Tango.MachineStudio.Storage_yjpbed13_wpftmp.csproj create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginMethod.cs create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/active_directory.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/login.png create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/machinestudio_login.png (limited to 'Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs') diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Storage/Tango.MachineStudio.Storage_yjpbed13_wpftmp.csproj b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Storage/Tango.MachineStudio.Storage_yjpbed13_wpftmp.csproj new file mode 100644 index 000000000..698c6fe82 --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Storage/Tango.MachineStudio.Storage_yjpbed13_wpftmp.csproj @@ -0,0 +1,154 @@ + + + + + Debug + AnyCPU + {5991F6B5-EA4E-41E9-A4F6-7D3A50010FD6} + library + Tango.MachineStudio.Storage + Tango.MachineStudio.Storage + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + true + full + false + ..\..\..\Build\Machine Studio\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\..\Build\Machine Studio\Release\ + TRACE + prompt + 4 + + + + + + GlobalVersionInfo.cs + + + + + + + + + + MainView.xaml + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + {f441feee-322a-4943-b566-110e12fd3b72} + Tango.BL + + + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} + Tango.Core + + + {4206ac58-3b57-4699-8835-90bf6db01a61} + Tango.Integration + + + {bc932dbd-7cdb-488c-99e4-f02cf441f55e} + Tango.Logging + + + {8491d07b-c1f6-4b62-a412-41b9fd2d6538} + Tango.SharedUI + + + {74e700b0-1156-4126-be40-ee450d3c3026} + Tango.Transport + + + {43135fb9-41db-4f87-9771-cf2c762027c0} + Tango.FirmwarePackageGenerator + + + {cb0b0aa2-bb24-4bca-a720-45e397684e12} + Tango.MachineStudio.Common + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Authentication/IAuthenticationProvider.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Authentication/IAuthenticationProvider.cs index 74969fd27..cb231fa05 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Authentication/IAuthenticationProvider.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Authentication/IAuthenticationProvider.cs @@ -29,7 +29,7 @@ namespace Tango.MachineStudio.Common.Authentication /// The email. /// The password. /// - AuthenticationLoginResult Login(String email, String password, bool bypassVersionCheck = false); + AuthenticationLoginResult Login(String email, String password, LoginMethod method, bool bypassVersionCheck = false); /// /// Logs-out the current logged-in user. diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/MachineStudioSettings.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/MachineStudioSettings.cs index 6ab26028f..91eaa857d 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/MachineStudioSettings.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/MachineStudioSettings.cs @@ -8,6 +8,7 @@ using System.Windows; using Tango.BL; using Tango.Integration.Operation; using Tango.Logging; +using Tango.MachineStudio.Common.Web; using Tango.PMR.Printing; using Tango.Settings; using Tango.Web; @@ -39,6 +40,11 @@ namespace Tango.MachineStudio.Common /// public String LastLoginPassword { get; set; } + /// + /// Gets or sets the last login method. + /// + public LoginMethod LastLoginMethod { get; set; } + /// /// Gets or sets a value indicating whether to save the user credentials. /// diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj index 5e874add1..2df984c7c 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Tango.MachineStudio.Common.csproj @@ -105,6 +105,7 @@ + @@ -426,7 +427,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginMethod.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginMethod.cs new file mode 100644 index 000000000..83f1c0850 --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginMethod.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.MachineStudio.Common.Web +{ + public enum LoginMethod + { + [Description("Active Directory")] + ActiveDirectory, + [Description("Standard User")] + StandardUser, + } +} diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginRequest.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginRequest.cs index 577f5e208..1727a2c6e 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginRequest.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginRequest.cs @@ -12,5 +12,6 @@ namespace Tango.MachineStudio.Common.Web public String Version { get; set; } public String Email { get; set; } public String Password { get; set; } + public LoginMethod Method { get; set; } } } diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Authentication/DefaultAuthenticationProvider.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Authentication/DefaultAuthenticationProvider.cs index c992d0768..209b26505 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Authentication/DefaultAuthenticationProvider.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Authentication/DefaultAuthenticationProvider.cs @@ -90,7 +90,7 @@ namespace Tango.MachineStudio.UI.Authentication /// The password. /// /// Login failed for user " + email - public AuthenticationLoginResult Login(string email, string password, bool bypassVersionCheck = false) + public AuthenticationLoginResult Login(string email, string password, LoginMethod method, bool bypassVersionCheck = false) { _refreshTokenTimer.Stop(); @@ -118,6 +118,7 @@ namespace Tango.MachineStudio.UI.Authentication Email = email, Password = password, Version = appVersion, + Method = method, }).Result; } diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/active_directory.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/active_directory.png new file mode 100644 index 000000000..4cced33e0 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/active_directory.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/login.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/login.png new file mode 100644 index 000000000..9f7d0b9ba Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/login.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/machinestudio_login.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/machinestudio_login.png new file mode 100644 index 000000000..98f1b286a Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/machinestudio_login.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj index 565489f0b..efe8fc6b1 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj @@ -363,6 +363,9 @@ TCC\template.bmp Always + + + @@ -676,7 +679,7 @@ if $(ConfigurationName) == Release RD /S /Q "$(TargetDir)Roslyn\" - + \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/LoginViewVM.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/LoginViewVM.cs index cf34764d9..9c2367f93 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/LoginViewVM.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/LoginViewVM.cs @@ -102,6 +102,21 @@ namespace Tango.MachineStudio.UI.ViewModels set { _enableSlotSelection = value; RaisePropertyChangedAuto(); } } + private bool _isActiveDirectory; + public bool IsActiveDirectory + { + get { return _isActiveDirectory; } + set { _isActiveDirectory = value; RaisePropertyChangedAuto(); if (value) IsStandardUser = false; } + } + + private bool _isStandardUser; + public bool IsStandardUser + { + get { return _isStandardUser; } + set { _isStandardUser = value; RaisePropertyChangedAuto(); if (value) IsActiveDirectory = false; } + } + + /// /// Gets or sets the login command. @@ -131,6 +146,15 @@ namespace Tango.MachineStudio.UI.ViewModels Email = _settings.LastLoginEmail; DeploymentSlot = _settings.DeploymentSlot; RememberMe = _settings.RememberMe; + + if (_settings.LastLoginMethod == LoginMethod.ActiveDirectory) + { + IsActiveDirectory = true; + } + else + { + IsStandardUser = true; + } try { @@ -154,11 +178,13 @@ namespace Tango.MachineStudio.UI.ViewModels IsLogging = true; InvalidateRelayCommands(); + LoginMethod loginMethod = IsActiveDirectory ? LoginMethod.ActiveDirectory : LoginMethod.StandardUser; + await Task.Factory.StartNew(() => { _settings.DeploymentSlot = DeploymentSlot; - LoginResponse result = _authenticationProvider.Login(Email, Password, _settings.ByPassEnvironmentVersionCheck).Response; + LoginResponse result = _authenticationProvider.Login(Email, Password, loginMethod, _settings.ByPassEnvironmentVersionCheck).Response; if (result.VersionChangeRequired && !_settings.ByPassEnvironmentVersionCheck) { @@ -183,6 +209,7 @@ namespace Tango.MachineStudio.UI.ViewModels _settings.LastLoginEmail = Email; _settings.RememberMe = RememberMe; + _settings.LastLoginMethod = loginMethod; _settings.LastLoginPassword = RememberMe ? cryptographer.Encrypt(Password) : null; _settings.Save(); diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/LoginView.xaml b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/LoginView.xaml index d93dbc127..e7428dd28 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/LoginView.xaml +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/LoginView.xaml @@ -45,35 +45,57 @@ Machine Studio - - + + + + + + + + + + - - - Login to your account - - - - - - - - - - - - - - Environment selection requires restarting the application - Remember me - + - - - Logging you in... - - - + + + Login to your account + + + + + + + + + + + + + + + + + + Environment selection requires restarting the application + Remember me + + + + + Logging you in... + + + + diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MainView.xaml b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MainView.xaml index 020343ba0..48f7b46d3 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MainView.xaml +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MainView.xaml @@ -55,7 +55,7 @@ IsChecked="{Binding Source={x:Reference MenuToggleButton}, Path=IsChecked, Mode=TwoWay}" /> - + diff --git a/Software/Visual_Studio/Tango.BL/ObservablesContextExtension.cs b/Software/Visual_Studio/Tango.BL/ObservablesContextExtension.cs index 263574f68..3d330b797 100644 --- a/Software/Visual_Studio/Tango.BL/ObservablesContextExtension.cs +++ b/Software/Visual_Studio/Tango.BL/ObservablesContextExtension.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Data.Entity; using System.Data.Entity.Core.Objects; using System.Data.Entity.Infrastructure; +using System.Data.SqlClient; using System.Data.SQLite; using System.IO; using System.Linq; @@ -25,18 +26,27 @@ namespace Tango.BL private ObservablesContextAdapter _adapter; private static DataSource _override_datasource; private DataSource _dataSource; + private static List _open_contexts; /// /// Gets a value indicating whether this instance is disposed. /// public bool IsDisposed { get; private set; } + /// + /// Initializes the class. + /// + static ObservablesContext() + { + _open_contexts = new List(); + } + /// /// Initializes a new instance of the class. /// public ObservablesContext() { - + _open_contexts.Add(this); } /// @@ -46,6 +56,7 @@ namespace Tango.BL /// if set to true will try to connect to an .mdf file. public ObservablesContext(DataSource dataSource) : base(dataSource.ToConnection(), true) { + _open_contexts.Add(this); _dataSource = dataSource; Database.SetInitializer(null); Configuration.LazyLoadingEnabled = false; @@ -286,6 +297,7 @@ namespace Tango.BL /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { + _open_contexts.Remove(this); base.Dispose(disposing); IsDisposed = true; } @@ -296,6 +308,16 @@ namespace Tango.BL { _override_datasource.AccessToken = accessToken; _override_datasource.AccessTokenExpiration = expiration; + + foreach (var context in _open_contexts.Where(x => x._dataSource.Type == DataSourceType.AccessToken)) + { + context._dataSource = _override_datasource; + var connection = context.Database.Connection as SqlConnection; + if (connection != null) + { + connection.AccessToken = context._dataSource.AccessToken; + } + } } } } diff --git a/Software/Visual_Studio/Tango.Web/SQLServer/SQLServerManager.cs b/Software/Visual_Studio/Tango.Web/SQLServer/SQLServerManager.cs index ce83d387c..8bc84f7b5 100644 --- a/Software/Visual_Studio/Tango.Web/SQLServer/SQLServerManager.cs +++ b/Software/Visual_Studio/Tango.Web/SQLServer/SQLServerManager.cs @@ -15,6 +15,7 @@ namespace Tango.Web.SQLServer public AuthenticationResult GetAccessToken() { var authContext = new AuthenticationContext(_service_root); + authContext.TokenCache.Clear(); ClientCredential clientCredentials = new ClientCredential(WebConfig.CLIENT_ID, WebConfig.APP_SECRET); AuthenticationResult authResult = authContext.AcquireTokenAsync("https://database.windows.net/", clientCredentials).Result; return authResult; diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs index da5ce16f7..0fd116361 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs +++ b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs @@ -286,20 +286,9 @@ namespace Tango.MachineService.Controllers public LoginResponse Login(LoginRequest request) { AuthenticationResult authResult = null; - - try - { - authResult = _ad_manager.ValidateUserCredentials(request.Email, request.Password); - } - catch (Exception ex) - { - throw new AuthenticationException(ex.FlattenMessage()); - } - - if (!_ad_manager.CanUserAccessCurrentEnvironment(request.Email)) - { - throw new AuthenticationException($"You do not have permissions to access the {MachineServiceConfig.DEPLOYMENT_SLOT.ToDescription()} environment."); - } + User user = null; + DataSource dataSource = null; + IHashGenerator hash = new BasicHashGenerator(); Version client_version; @@ -310,84 +299,122 @@ namespace Tango.MachineService.Controllers bool versionChangeRequired = false; String requiredVersion = null; + bool isPasswordOK = false; - User user = null; - - using (ObservablesContext db = ObservablesContextHelper.CreateContext()) + try { - db.Roles.ToList(); - db.Permissions.ToList(); - db.UsersRoles.ToList(); - db.RolesPermissions.ToList(); + authResult = _ad_manager.ValidateUserCredentials(request.Email, request.Password); + isPasswordOK = true; + } + catch {} - user = new UserBuilder(db).Set(x => x.Email.ToLower() == request.Email.ToLower()).WithRolesAndPermissions().WithDeleted().Build(); + //Login via Active Directory + if (request.Method == LoginMethod.ActiveDirectory) + { + try + { + authResult = _ad_manager.ValidateUserCredentials(request.Email, request.Password); + } + catch (Exception ex) + { + throw new AuthenticationException(ex.FlattenMessage()); + } - IHashGenerator g = new BasicHashGenerator(); + if (!_ad_manager.CanUserAccessCurrentEnvironment(request.Email)) + { + throw new AuthenticationException($"You do not have permissions to access the {MachineServiceConfig.DEPLOYMENT_SLOT.ToDescription()} environment."); + } - if (user == null) + using (ObservablesContext db = ObservablesContextHelper.CreateContext()) { - //Than add the user !! - User new_user = new User(); - new_user.Email = request.Email; - new_user.Password = g.Encrypt(request.Password); - new_user.Organization = db.Organizations.Include(x => x.Address).Single(x => x.Name == "Twine"); - new_user.Address = new_user.Organization.Address.Clone(); - new_user.Contact = new Contact() - { - FirstName = authResult.UserInfo.GivenName, - LastName = authResult.UserInfo.FamilyName, - FullName = authResult.UserInfo.GivenName + " " + authResult.UserInfo.FamilyName, - Email = request.Email, - }; + db.Roles.ToList(); + db.Permissions.ToList(); + db.UsersRoles.ToList(); + db.RolesPermissions.ToList(); - db.UsersRoles.Add(new UsersRole() - { - User = new_user, - Role = db.Roles.Single(x => (Roles)x.Code == Roles.User), - }); + user = new UserBuilder(db).Set(x => x.Email.ToLower() == request.Email.ToLower()).WithRolesAndPermissions().WithDeleted().Build(); - db.UsersRoles.Add(new UsersRole() + if (user == null) { - User = new_user, - Role = db.Roles.Single(x => (Roles)x.Code == Roles.MachineStudioUser), - }); + user = new User(); + user.Email = request.Email; + user.Password = hash.Encrypt(request.Password); + user.Organization = db.Organizations.Include(x => x.Address).Single(x => x.Name == "Twine"); + user.Address = user.Organization.Address.Clone(); + user.Contact = new Contact() + { + FirstName = authResult.UserInfo.GivenName, + LastName = authResult.UserInfo.FamilyName, + FullName = authResult.UserInfo.GivenName + " " + authResult.UserInfo.FamilyName, + Email = request.Email, + }; - new_user.LastLogin = DateTime.UtcNow; - db.Users.Add(new_user); - } - else - { - if (user.Deleted) + db.UsersRoles.Add(new UsersRole() + { + User = user, + Role = db.Roles.Single(x => (Roles)x.Code == Roles.User), + }); + + db.UsersRoles.Add(new UsersRole() + { + User = user, + Role = db.Roles.Single(x => (Roles)x.Code == Roles.MachineStudioUser), + }); + + user.Password = hash.Encrypt(request.Password); + + db.Users.Add(user); + } + else { - throw new AuthenticationException("Your account has been disabled. Please contact your administrator."); + if (user.Deleted) + { + throw new AuthenticationException("Your account has been disabled. Please contact your administrator."); + } } user.LastLogin = DateTime.UtcNow; - user.Password = g.Encrypt(request.Password); + + db.SaveChanges(); } - db.SaveChanges(); + dataSource = new DataSource() + { + Address = MachineServiceConfig.DB_ADDRESS, + Catalog = MachineServiceConfig.DB_CATALOG, + Type = Core.DataSourceType.Azure, + IntegratedSecurity = false, + UserName = request.Email, + Password = request.Password, + }; + } + //Login via Database standard user + else + { + var password = hash.Encrypt(request.Password); - if (MachineServiceConfig.ENFORCE_MACHINE_STUDIO_VERSION) + using (var db = ObservablesContextHelper.CreateContext()) { - var latest_version = db.MachineStudioVersions.ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); + user = new UserBuilder(db).Set(x => x.Email.ToLower() == request.Email.ToLower() && (isPasswordOK || x.Password == password)).WithRolesAndPermissions().WithDeleted().Build(); - if (latest_version != null && Version.Parse(latest_version.Version) != client_version) + if (user == null) { - versionChangeRequired = true; - requiredVersion = latest_version.Version; + throw new AuthenticationException("Invalid email or password."); } - } - } - Core.DataSource dataSource = null; + if (user.Deleted) + { + throw new AuthenticationException("Your account has been disabled. Please contact your administrator."); + } + + user.LastLogin = DateTime.UtcNow; + db.SaveChanges(); + } - if (MachineServiceConfig.USE_DB_ACCESS_TOKENS) - { SQLServerManager sqlServer = new SQLServerManager(); var accessToken = sqlServer.GetAccessToken(); - dataSource = new Core.DataSource() + dataSource = new DataSource() { Address = MachineServiceConfig.DB_ADDRESS, Catalog = MachineServiceConfig.DB_CATALOG, @@ -397,19 +424,23 @@ namespace Tango.MachineService.Controllers AccessTokenExpiration = accessToken.ExpiresOn.UtcDateTime }; } - else + + //Enforce Machine Studio Version ? + if (MachineServiceConfig.ENFORCE_MACHINE_STUDIO_VERSION) { - dataSource = new Core.DataSource() + using (var db = ObservablesContextHelper.CreateContext()) { - Address = MachineServiceConfig.DB_ADDRESS, - Catalog = MachineServiceConfig.DB_CATALOG, - Type = Core.DataSourceType.Azure, - IntegratedSecurity = false, - UserName = request.Email, - Password = request.Password, - }; + var latest_version = db.MachineStudioVersions.ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); + + if (latest_version != null && Version.Parse(latest_version.Version) != client_version) + { + versionChangeRequired = true; + requiredVersion = latest_version.Version; + } + } } + //Return data source return new LoginResponse() { DataSource = dataSource, -- cgit v1.3.1 From 7ffd0bab3c6f397936f8ef9f6829cdf33b850efa Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Sun, 19 Jan 2020 16:10:55 +0200 Subject: Implemented new ms user creation + PASSWORD_CHANGE_REQUIRED. Improved ms login design + password change. Improved ms main menu and current user design. --- Software/DB/PPC/Tango.mdf | Bin 75497472 -> 75497472 bytes Software/DB/PPC/Tango_log.ldf | Bin 53673984 -> 53673984 bytes .../DB/SQLExaminer Projects/LOCAL TO DEV.seproj | 68 +++++++++ Software/DB/Tango.mdf | Bin 75497472 -> 75497472 bytes Software/DB/Tango_log.ldf | Bin 22675456 -> 22675456 bytes .../Views/MachineCreationDialog.xaml | 2 +- .../Images/login.png | Bin 0 -> 6401 bytes .../Tango.MachineStudio.UsersAndRoles.csproj | 21 ++- .../ViewModels/MainViewVM.cs | 31 ++-- .../ViewModels/UserCreationDialogVM.cs | 74 ++++++++++ .../Views/UserCreationDialog.xaml | 53 +++++++ .../Views/UserCreationDialog.xaml.cs | 28 ++++ .../Views/UserManagementView.xaml | 2 +- .../Views/UserView.xaml | 2 - .../packages.config | 1 + .../Web/LoginResponse.cs | 1 + .../DefaultAuthenticationProvider.cs | 8 ++ .../Tango.MachineStudio.UI/Images/login_white.png | Bin 0 -> 3308 bytes .../Tango.MachineStudio.UI.csproj | 1 + .../ViewModels/LoginViewVM.cs | 135 +++++++++++++++++- .../Tango.MachineStudio.UI/Views/LoginView.xaml | 45 +++++- .../Tango.MachineStudio.UI/Views/MainView.xaml | 75 +++++----- .../Visual_Studio/Tango.BL/DTO/JobRunDTOBase.cs | 8 +- Software/Visual_Studio/Tango.BL/DTO/UserDTOBase.cs | 8 ++ .../Visual_Studio/Tango.BL/Entities/JobRunBase.cs | 52 +++---- Software/Visual_Studio/Tango.BL/Entities/User.cs | 39 ------ .../Visual_Studio/Tango.BL/Entities/UserBase.cs | 38 +++++ .../Visual_Studio/Tango.DAL.Remote/DB/JOB_RUNS.cs | 2 +- .../Tango.DAL.Remote/DB/RemoteADO.edmx | 9 +- .../Tango.DAL.Remote/DB/RemoteADO.edmx.diagram | 156 ++++++++++----------- Software/Visual_Studio/Tango.DAL.Remote/DB/USER.cs | 1 + .../Controllers/MachineStudioController.cs | 3 +- 32 files changed, 649 insertions(+), 214 deletions(-) create mode 100644 Software/DB/SQLExaminer Projects/LOCAL TO DEV.seproj create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Images/login.png create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/ViewModels/UserCreationDialogVM.cs create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserCreationDialog.xaml create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserCreationDialog.xaml.cs create mode 100644 Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/login_white.png (limited to 'Software/Visual_Studio/Web/Tango.MachineService/Controllers/MachineStudioController.cs') diff --git a/Software/DB/PPC/Tango.mdf b/Software/DB/PPC/Tango.mdf index d11f45fb6..b662518b9 100644 Binary files a/Software/DB/PPC/Tango.mdf and b/Software/DB/PPC/Tango.mdf differ diff --git a/Software/DB/PPC/Tango_log.ldf b/Software/DB/PPC/Tango_log.ldf index 34e1b9a60..2c32260dc 100644 Binary files a/Software/DB/PPC/Tango_log.ldf and b/Software/DB/PPC/Tango_log.ldf differ diff --git a/Software/DB/SQLExaminer Projects/LOCAL TO DEV.seproj b/Software/DB/SQLExaminer Projects/LOCAL TO DEV.seproj new file mode 100644 index 000000000..ba509261c --- /dev/null +++ b/Software/DB/SQLExaminer Projects/LOCAL TO DEV.seproj @@ -0,0 +1,68 @@ + + + + MsSqlServer + LiveDb + localhost\sqlexpress + Tango + True + + + MsSqlAzure + LiveDb + twine.database.windows.net + Tango_DEV + False + SqlServer + Roy + Aa123456 + true + + + + + + + + false + false + false + false + false + false + false + false + true + false + false + false + true + true + true + true + false + true + false + false + false + false + true + false + false + false + false + false + false + false + true + false + false + true + + + + + + + + \ No newline at end of file diff --git a/Software/DB/Tango.mdf b/Software/DB/Tango.mdf index fbfed2eae..966874e5f 100644 Binary files a/Software/DB/Tango.mdf and b/Software/DB/Tango.mdf differ diff --git a/Software/DB/Tango_log.ldf b/Software/DB/Tango_log.ldf index 6099b8a73..5b42f3fba 100644 Binary files a/Software/DB/Tango_log.ldf and b/Software/DB/Tango_log.ldf differ diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.MachineDesigner/Views/MachineCreationDialog.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.MachineDesigner/Views/MachineCreationDialog.xaml index e3ba1bff4..2d380c0d2 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.MachineDesigner/Views/MachineCreationDialog.xaml +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.MachineDesigner/Views/MachineCreationDialog.xaml @@ -8,7 +8,7 @@ xmlns:vm="clr-namespace:Tango.MachineStudio.MachineDesigner.ViewModels" xmlns:local="clr-namespace:Tango.MachineStudio.MachineDesigner.Views" mc:Ignorable="d" - d:DesignHeight="400" d:DesignWidth="700" Height="400" Width="700" Background="White" d:DataContext="{d:DesignInstance Type=vm:MachineCreationDialogVM, IsDesignTimeCreatable=False}"> + d:DesignHeight="400" d:DesignWidth="700" Height="400" Width="700" Background="{StaticResource WhiteBackgroundBrush}" d:DataContext="{d:DesignInstance Type=vm:MachineCreationDialogVM, IsDesignTimeCreatable=False}"> diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Images/login.png b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Images/login.png new file mode 100644 index 000000000..9f7d0b9ba Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Images/login.png differ diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Tango.MachineStudio.UsersAndRoles.csproj b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Tango.MachineStudio.UsersAndRoles.csproj index 82376b751..035cb2b9d 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Tango.MachineStudio.UsersAndRoles.csproj +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Tango.MachineStudio.UsersAndRoles.csproj @@ -49,6 +49,9 @@ ..\..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + ..\..\..\packages\SimpleValidator.0.6.1.0\lib\net40\SimpleValidator.dll + @@ -80,6 +83,7 @@ + AddressView.xaml @@ -95,6 +99,9 @@ OrganizationSelectionView.xaml + + UserCreationDialog.xaml + UserManagementView.xaml @@ -120,7 +127,9 @@ ResXFileCodeGenerator Resources.Designer.cs - + + Designer + SettingsSingleFileGenerator @@ -186,6 +195,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -210,11 +223,13 @@ - + + + - + \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/ViewModels/MainViewVM.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/ViewModels/MainViewVM.cs index 2f4e5c9a3..81ef04dd4 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/ViewModels/MainViewVM.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/ViewModels/MainViewVM.cs @@ -370,20 +370,19 @@ namespace Tango.MachineStudio.UsersAndRoles.ViewModels } } - private async void AddNewUser() + private void AddNewUser() { - String email = _notification.ShowTextInput("Enter user email", "email"); - - if (!String.IsNullOrWhiteSpace(email)) + _notification.ShowModalDialog(async (vm) => { User user = new User(); - user.Email = email; - user.Password = "1111"; + user.Email = vm.Email; + user.Password = User.GetPasswordHash(vm.Password); + user.PasswordChangeRequired = true; user.Contact = new Contact() { - FirstName = "Twine", - LastName = "User", - Email = email, + FirstName = vm.FirstName, + LastName = vm.LastName, + Email = vm.Email, }; user.Address = new Address(); @@ -394,6 +393,18 @@ namespace Tango.MachineStudio.UsersAndRoles.ViewModels Role = _manageContext.Roles.SingleOrDefault(x => x.Code == (int)BL.Enumerations.Roles.User) }); + user.UsersRoles.Add(new UsersRole() + { + User = user, + Role = _manageContext.Roles.SingleOrDefault(x => x.Code == (int)BL.Enumerations.Roles.MachineStudioUser) + }); + + user.UsersRoles.Add(new UsersRole() + { + User = user, + Role = _manageContext.Roles.SingleOrDefault(x => x.Code == (int)BL.Enumerations.Roles.PPCUser) + }); + try { user.Validate(_manageContext); @@ -413,7 +424,7 @@ namespace Tango.MachineStudio.UsersAndRoles.ViewModels await LoadOrganizations(); SelectedOrganization = Organizations.SingleOrDefault(x => x.Guid == ManagedOrganization.Guid); } - } + }); } private void SetUserPlace(Place place) diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/ViewModels/UserCreationDialogVM.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/ViewModels/UserCreationDialogVM.cs new file mode 100644 index 000000000..08762ac96 --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/ViewModels/UserCreationDialogVM.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SimpleValidator.Extensions; +using Tango.SharedUI; + +namespace Tango.MachineStudio.UsersAndRoles.ViewModels +{ + public class UserCreationDialogVM : DialogViewVM + { + private static Random rnd = new Random(); + + private String _email; + [Required(ErrorMessage = "Email is required")] + [EmailAddress(ErrorMessage = "Please provide a valid email")] + public String Email + { + get { return _email; } + set { _email = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } + } + + private String _password; + public String Password + { + get { return _password; } + set { _password = value; RaisePropertyChangedAuto(); } + } + + private String _firstName; + [Required(ErrorMessage = "First name is required")] + public String FirstName + { + get { return _firstName; } + set { _firstName = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } + } + + private String _lastName; + [Required(ErrorMessage = "Last name is required")] + public String LastName + { + get { return _lastName; } + set { _lastName = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } + } + + protected override void Accept() + { + if (Validate()) + { + base.Accept(); + } + } + + public override void OnShow() + { + base.OnShow(); + Password = GetRandomPassword(4); + } + + private String GetRandomPassword(int count) + { + String pass = String.Empty; + + for (int i = 0; i < count; i++) + { + pass += rnd.Next(0, 9).ToString(); + } + + return pass; + } + } +} diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserCreationDialog.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserCreationDialog.xaml new file mode 100644 index 000000000..7433d3768 --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserCreationDialog.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + NEW USER + + + + + + + + + + + + + + EMAIL + + FIRST NAME + + LAST NAME + + + + + Provide this password to the user + + + + + diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserCreationDialog.xaml.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserCreationDialog.xaml.cs new file mode 100644 index 000000000..cfa389ed1 --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserCreationDialog.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Tango.MachineStudio.UsersAndRoles.Views +{ + /// + /// Interaction logic for UserCreationDialog.xaml + /// + public partial class UserCreationDialog : UserControl + { + public UserCreationDialog() + { + InitializeComponent(); + } + } +} diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserManagementView.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserManagementView.xaml index 5246ae09c..3964abfc8 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserManagementView.xaml +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserManagementView.xaml @@ -50,7 +50,7 @@ LOGIN - + diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserView.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserView.xaml index 0858d7e08..37f649a7a 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserView.xaml +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/Views/UserView.xaml @@ -16,8 +16,6 @@ - - diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/packages.config b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/packages.config index fe4f26e87..8696cb880 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/packages.config +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.UsersAndRoles/packages.config @@ -7,4 +7,5 @@ + \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginResponse.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginResponse.cs index 4ae22fa93..3515c32d1 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginResponse.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.Common/Web/LoginResponse.cs @@ -14,5 +14,6 @@ namespace Tango.MachineStudio.Common.Web public DataSource DataSource { get; set; } public bool VersionChangeRequired { get; set; } public String RequiredVersion { get; set; } + public bool PasswordChangeRequired { get; set; } } } diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Authentication/DefaultAuthenticationProvider.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Authentication/DefaultAuthenticationProvider.cs index 0131cd209..26938b203 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Authentication/DefaultAuthenticationProvider.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Authentication/DefaultAuthenticationProvider.cs @@ -147,6 +147,14 @@ namespace Tango.MachineStudio.UI.Authentication }; } + if (response.PasswordChangeRequired) + { + return new AuthenticationLoginResult() + { + Response = response + }; + } + try { ObservablesStaticCollections.Instance.Initialize((x) => diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/login_white.png b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/login_white.png new file mode 100644 index 000000000..10a054147 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Images/login_white.png differ diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj index 13f22dfda..0525c2351 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Tango.MachineStudio.UI.csproj @@ -363,6 +363,7 @@ TCC\template.bmp Always + diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/LoginViewVM.cs b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/LoginViewVM.cs index dce469dbd..c00caf72a 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/LoginViewVM.cs +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/ViewModels/LoginViewVM.cs @@ -22,6 +22,9 @@ using Tango.MachineStudio.UI.Messages; using Tango.Settings; using Tango.SharedUI; using Tango.Web; +using SimpleValidator.Extensions; +using Tango.BL.Entities; +using System.Data.Entity; namespace Tango.MachineStudio.UI.ViewModels { @@ -38,6 +41,7 @@ namespace Tango.MachineStudio.UI.ViewModels private Rfc2898Cryptographer cryptographer; private MachineStudioSettings _settings; private MachineStudioWebClient _machineStudioWebClient; + private TaskCompletionSource _updatePasswordCompletionSource; private String _email; /// @@ -82,6 +86,14 @@ namespace Tango.MachineStudio.UI.ViewModels set { _isLogging = value; RaisePropertyChangedAuto(); } } + private bool _showLogginDetails; + public bool ShowLoggingDetails + { + get { return _showLogginDetails; } + set { _showLogginDetails = value; RaisePropertyChangedAuto(); } + } + + private bool _rememberMe; /// /// Gets or sets a value indicating whether to remember the last user email and password. @@ -123,11 +135,37 @@ namespace Tango.MachineStudio.UI.ViewModels set { _progressLog = value; RaisePropertyChangedAuto(); } } + private bool _isChangingPassword; + public bool IsChangingPassword + { + get { return _isChangingPassword; } + set { _isChangingPassword = value; RaisePropertyChangedAuto(); } + } + + private String _newPassword1; + public String NewPassword1 + { + get { return _newPassword1; } + set { _newPassword1 = value; RaisePropertyChangedAuto(); } + } + + private String _newPassword2; + public String NewPassword2 + { + get { return _newPassword2; } + set { _newPassword2 = value; RaisePropertyChangedAuto(); } + } + /// /// Gets or sets the login command. /// public RelayCommand LoginCommand { get; set; } + /// + /// Gets or sets the update password command. + /// + public RelayCommand UpdatePasswordCommand { get; set; } + /// /// Initializes a new instance of the class. /// @@ -137,6 +175,7 @@ namespace Tango.MachineStudio.UI.ViewModels public LoginViewVM(MachineStudioWebClient machineStudioWebClient, IAuthenticationProvider authenticationProvider, INavigationManager navigationManager, INotificationProvider notificationProvider, IEventLogger eventLogger) { EnableSlotSelection = true; + ShowLoggingDetails = true; _machineStudioWebClient = machineStudioWebClient; _settings = SettingsManager.Default.GetOrCreate(); @@ -146,6 +185,7 @@ namespace Tango.MachineStudio.UI.ViewModels _authenticationProvider = authenticationProvider; _eventLogger = eventLogger; LoginCommand = new RelayCommand(Login, () => !IsLogging); + UpdatePasswordCommand = new RelayCommand(UpdatePassword, () => IsChangingPassword); cryptographer = new Rfc2898Cryptographer(); Email = _settings.LastLoginEmail; @@ -181,6 +221,10 @@ namespace Tango.MachineStudio.UI.ViewModels try { IsLogging = true; + ShowLoggingDetails = false; + NewPassword1 = String.Empty; + NewPassword2 = String.Empty; + InvalidateRelayCommands(); LoginMethod loginMethod = IsActiveDirectory ? LoginMethod.ActiveDirectory : LoginMethod.StandardUser; @@ -190,9 +234,9 @@ namespace Tango.MachineStudio.UI.ViewModels _settings.DeploymentSlot = DeploymentSlot; LoginResponse result = _authenticationProvider.Login(Email, Password, loginMethod, _settings.ByPassEnvironmentVersionCheck, (progress) => - { - ProgressLog = progress; - }).Response; + { + ProgressLog = progress; + }).Response; if (result.VersionChangeRequired && !_settings.ByPassEnvironmentVersionCheck) { @@ -211,6 +255,14 @@ namespace Tango.MachineStudio.UI.ViewModels return; } + if (result.PasswordChangeRequired) + { + StartUpdatePassword().Task.GetAwaiter().GetResult(); + Password = NewPassword1; + Login(); + return; + } + _eventLogger.Log(EventTypes.APPLICATION_STARTED, "Application Started!"); _navigationManager.NavigateTo(NavigationView.MainView); @@ -224,19 +276,94 @@ namespace Tango.MachineStudio.UI.ViewModels _eventLogger.Log("User logged in."); EnableSlotSelection = false; + + IsLogging = false; + ShowLoggingDetails = true; + IsChangingPassword = false; + InvalidateRelayCommands(); }); } catch (Exception ex) { + IsLogging = false; + ShowLoggingDetails = true; + IsChangingPassword = false; + InvalidateRelayCommands(); LogManager.Log(ex, "Login Error."); _notificationProvider.ShowError($"An error occurred while trying to perform the log-in operation.\n{ex.FlattenMessage()}"); } - finally + } + } + + private TaskCompletionSource StartUpdatePassword() + { + _updatePasswordCompletionSource = new TaskCompletionSource(); + + IsChangingPassword = true; + ShowLoggingDetails = false; + IsLogging = false; + InvalidateRelayCommands(); + + return _updatePasswordCompletionSource; + } + + private async void UpdatePassword() + { + await Task.Factory.StartNew(() => + { + try + { + if (!Validate()) + { + return; + } + + ProgressLog = "Updating your password..."; + IsChangingPassword = false; + IsLogging = true; + InvalidateRelayCommands(); + + using (var db = ObservablesContext.CreateDefault()) + { + var user = db.Users.SingleOrDefault(x => x.Email == Email); + user.PasswordChangeRequired = false; + user.Password = User.GetPasswordHash(NewPassword1); + db.SaveChanges(); + } + + _updatePasswordCompletionSource.SetResult(true); + } + catch (Exception ex) { IsLogging = false; + IsChangingPassword = false; + ShowLoggingDetails = true; + InvalidateRelayCommands(); + _updatePasswordCompletionSource.SetException(ex); + } + finally + { InvalidateRelayCommands(); } + }); + } + + protected override void OnValidating() + { + if (IsChangingPassword) + { + if (!NewPassword1.IsBetweenLength(6, 8)) + { + InsertError(nameof(NewPassword1), "Password must be 6 to 8 characters long"); + } + + if (NewPassword1 != NewPassword2) + { + InsertError(nameof(NewPassword2), "Passwords do not match"); + } } + + base.OnValidating(); } } } diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/LoginView.xaml b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/LoginView.xaml index ff13ec2c7..9a3b3405e 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/LoginView.xaml +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/LoginView.xaml @@ -47,7 +47,7 @@ - + @@ -57,10 +57,36 @@ - + + + + + - + Login to your account @@ -97,6 +123,19 @@ + + + Password change required + + + + + + + + + + diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MainView.xaml b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MainView.xaml index 48f7b46d3..2d5a5c3aa 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MainView.xaml +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/MainView.xaml @@ -50,48 +50,19 @@ + + - - - - - - - - , - - - - - - - - - - - - - - - ... - - - - - - - - - - - - Home - - + MODULES @@ -148,8 +119,36 @@ - + + + + + + + + + , + + + + + + + + + + + + + + + ... + + + +