using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.Core; using Tango.Core.Commands; using Tango.FSE.Common; using Tango.FSE.Common.FileAssociation; using Tango.FSE.Common.Navigation; using Tango.FSE.Procedures.Messages; using Tango.FSE.Procedures.Navigation; using Tango.FSE.Procedures.Views; namespace Tango.FSE.Procedures.ViewModels { public class ProcedureRunnerViewVM : FSEViewModel, IProcedureLogger, INavigationObjectReceiver { public enum RunnerView { ProcedureRunnerCatalogView, ProcedureRunnerExecutionView } private bool _requiresReloadingOfProjects; private bool _isFromNavigation; private RunProcedureNavigationObject _navigationObject; private RunnerView _selectedView; public RunnerView SelectedView { get { return _selectedView; } set { _selectedView = value; RaisePropertyChangedAuto(); } } private List _publishedProcedureProjects; public List PublishedProcedureProjects { get { return _publishedProcedureProjects; } set { _publishedProcedureProjects = value; RaisePropertyChangedAuto(); } } private ProcedureProject _runningProcedureProject; public ProcedureProject RunningProcedureProject { get { return _runningProcedureProject; } set { _runningProcedureProject = value; RaisePropertyChangedAuto(); } } private ProjectRunner _projectRunner; public ProjectRunner ProjectRunner { get { return _projectRunner; } set { _projectRunner = value; RaisePropertyChangedAuto(); } } private bool _isLoadingProjects; public bool IsLoadingProjects { get { return _isLoadingProjects; } set { _isLoadingProjects = value; RaisePropertyChangedAuto(); } } public bool HasProcedureInputs { get { return RunningProcedureProject != null && RunningProcedureProject.Inputs.Count > 0; } } public ResultsViewVM ResultsViewVM { get; set; } private String _status; public String Status { get { return _status; } set { _status = value; RaisePropertyChangedAuto(); } } private String _failedError; public String FailedError { get { return _failedError; } set { _failedError = value; RaisePropertyChangedAuto(); } } private bool _isRunning; public bool IsRunning { get { return _isRunning; } set { _isRunning = value; RaisePropertyChangedAuto(); } } private TangoProgress _procedureProgress; public TangoProgress ProcedureProgress { get { return _procedureProgress; } set { _procedureProgress = value; RaisePropertyChangedAuto(); } } public RelayCommand EditProjectCommand { get; set; } public RelayCommand RunProjectCommand { get; set; } public RelayCommand StartProjectCommand { get; set; } public RelayCommand StopProjectCommand { get; set; } public ProcedureRunnerViewVM() { ResultsViewVM = new ResultsViewVM(); EditProjectCommand = new RelayCommand(EditProject); RunProjectCommand = new RelayCommand(RunProject); StartProjectCommand = new RelayCommand(StartProject, () => ProjectRunner != null && ProjectRunner.CanRun); StopProjectCommand = new RelayCommand(StopProject, () => ProjectRunner != null && ProjectRunner.IsRunning); _requiresReloadingOfProjects = true; RegisterForMessage((x) => _requiresReloadingOfProjects = true); } public override void OnApplicationStarted() { base.OnApplicationStarted(); FileAssociationProvider.RegisterFileAssociationHandler("procedure", HandlerProcedureFileAssociation); } private async void StartProject() { try { if (IsRunning) { await NotificationProvider.ShowError("Cannot execute a procedure while another procedure is running."); return; } IsRunning = true; FailedError = null; ResultsViewVM.Results = new List(); Status = "Running..."; var context = new ProcedureContext(RunningProcedureProject, this); ProcedureProgress = new TangoProgress(null); context.Progress += Context_Progress; await ProjectRunner.Run(context); Status = "Completed"; ResultsViewVM.Results = context.Results.ToList(); } catch (OperationCanceledException) { Status = "Stopped"; } catch (Exception ex) { Status = "Failed"; FailedError = ex.FlattenMessage(); } finally { IsRunning = false; ProcedureProgress = new TangoProgress(null, false, 0, 100); } } private void Context_Progress(object sender, TangoProgressChangedEventArgs e) { ProcedureProgress = e.Progress; } private void StopProject() { ProjectRunner.Stop(); } private void EditProject(PublishedProcedureProject project) { if (project != null) { NavigationManager.NavigateWithObject< ProceduresModule, ProcedureDesignerView, ProcedureDesignerViewVM.NavigationObject>( new ProcedureDesignerViewVM.NavigationObject() { Project = project }); } } public override void OnNavigatedTo() { base.OnNavigatedTo(); if (_requiresReloadingOfProjects) { LoadPublishedProcedureProjects(); } if (_isFromNavigation) { if (!IsRunning) { RunningProcedureProject = _navigationObject.Project; RaisePropertyChanged(nameof(HasProcedureInputs)); ProjectRunner = new ProjectRunner(RunningProcedureProject); ProjectRunner.StateChanged += (x, e) => InvalidateRelayCommands(); Status = "Ready"; InvalidateRelayCommands(); SelectedView = RunnerView.ProcedureRunnerExecutionView; if (_navigationObject.StartProcedure) { StartProject(); } } else { NotificationProvider.ShowError("Cannot execute a procedure while another procedure is running."); } } } private async void LoadPublishedProcedureProjects() { try { IsFree = false; IsLoadingProjects = true; PublishedProcedureProjects = new List(); PublishedProcedureProjects = await Services.PublishedProcedureProjectsService.GetPublishedProcedureProjects(); _requiresReloadingOfProjects = false; IsFree = true; } catch (Exception ex) { IsFree = false; LogManager.Log(ex, "Error retrieving published procedure projects."); await NotificationProvider.ShowError($"Error occurred while trying to retrieve the procedures collection.\n{ex.FlattenMessage()}"); } finally { IsLoadingProjects = false; } } private void RunProject(PublishedProcedureProject project) { RunningProcedureProject = ProcedureProject.FromJson(project.CurrentVersion.ProjectJsonString); RaisePropertyChanged(nameof(HasProcedureInputs)); ProjectRunner = new ProjectRunner(RunningProcedureProject); ProjectRunner.StateChanged += (x, e) => InvalidateRelayCommands(); Status = "Ready"; InvalidateRelayCommands(); SelectedView = RunnerView.ProcedureRunnerExecutionView; } public override Task OnNavigateBackRequest() { if (SelectedView == RunnerView.ProcedureRunnerExecutionView && !IsRunning) { SelectedView = RunnerView.ProcedureRunnerCatalogView; if (_isFromNavigation) { _isFromNavigation = false; return Task.FromResult(true); } return Task.FromResult(false); } _isFromNavigation = false; return base.OnNavigateBackRequest(); } public void WriteLine(string text) { Status = text; } public void Write(string text) { Status = text; } public void Clear() { //Do nothing } public void OnNavigatedToWithObject(RunProcedureNavigationObject obj) { _isFromNavigation = true; _navigationObject = obj; } private async void HandlerProcedureFileAssociation(FileAssociationPackage package) { if (!CurrentUser.HasPermission(Permissions.FSE_RunProcedureProjectFile)) { await NotificationProvider.ShowError("Current user profile does not allow loading unpublished procedures."); return; } if (File.Exists(package.File)) { try { ProcedureProject project = ProcedureProject.FromJson(File.ReadAllText(package.File)); LogManager.Log("Loading procedure from file association..."); await NavigationManager.NavigateWithObject(new RunProcedureNavigationObject() { Project = project }); } catch (Exception ex) { LogManager.Log(ex, "Error occurred while trying to handler the procedure file association."); } } } } }