diff --git a/README.md b/README.md index 756cb8f..3690e7f 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,6 @@ EasyMorph Server Command Line Client (in further text – **ems-cmd**) allows yo #### Download ems-cmd comes together with EasyMorph Server. Also it can be [downloaded](https://github.com/easymorph/server-cmd/releases) separately -Current version is 1.2.0. - #### General command format: @@ -33,6 +31,13 @@ ems-cmd may return one of the following exit codes: * `0` ems-cmd was successfully run to completion. * `1` A fatal error occurred during command parsing or execution. +#### Authorization +For password protected spaces you should pass password via command parameter `-password`. +``` +ems-cmd upload http://192.168.100.200:6330 -space Default -password your_password -source D:\your\local\folder\file.xml -target \ +``` +In the example above, a session will be opened for the specified space Default, of course if password was correct. In case of incorrect password or public spaces, error will be thrown. +Some hash computations are applied to the password before it is sent to the server. ### Commands @@ -53,8 +58,50 @@ Retrieving server status... STATUS: StatusCode: OK StatusMessage: Server is OK -ServerVersion:1.2.0.0 +ServerVersion:1.3.0.0 +``` + + +#### Retrieve spaces list +A list of all spaces will be displayed. This command doesn't require authorization. + +```bash +ems-cmd listspaces http://192.168.100.200:6330 +``` +###### Parameters +This command has no additional parameters + +###### Output ``` +Available spaces: +* closed one + Default +Listing done +``` +Asterisk `*` means that the space requires an authorization. + + +#### Space status +Returns specified space status. This command may require authorization if space is password protected. + +```bash +ems-cmd spacestatus http://192.168.100.200:6330 -space "closed one" -password some_password +``` +###### Parameters + +* `-space` - space name. +* `-password` - if password is required. + +###### Output +``` +Checking space default status... +Space: Default +IsPublic: True +Permissions: FilesList, FileDownload +done +``` + + ### Tasks Related #### Start the task @@ -62,15 +109,36 @@ This command will start specified task and wait until it is done. To start the task you need to know space name and the task ID. Make sure to check the task execution server log to determine task execution info. + + ``` ems-cmd run http://192.168.100.200:6330 -space Default -taskID 59b824f0-4b81-453f-b9e0-1c58b97c9fb9 ``` ###### Parameters * `-space` - space name, e.g. `Default` * `-taskID` - task guid. +* `-param:XXX ZZ` - set task parameter `XXX` with value `ZZ`. Task guid can be found in the browser location toolbar. E.g, if you have clicked on the edit task link, your browser location seems to be `http://localhost:6330/default/tasks/edit/59b824f0-4b81-453f-b9e0-1c58b97c9fb9`, where `59b824f0-4b81-453f-b9e0-1c58b97c9fb9` - is a desired value +If you want to pass (or override) parameters that were defined in morph project, add `-param:XXX ZZ` to ems-cmd execution line. +Where `XXX` is a parameter name and `ZZ` is a parameter value. +At least one space between parameter name and parameter value is required. + + +E.g. If you've defined parameter `Timeout` in your morph project, and want to set it to 73 use `-param:Timeout 73`. Pay attention, that parameters are case sensitive. + + +Examples: + + +Set parameter `Rounds` to `10` : `-param:Timeout 73` + +Set parameter `Full name` to `John Smith` : `-param:"Full name" "John Smith"` + +Set parameter `From Date` to the `10th of December 2000` : `-param:"From Date" "2000-12-10"` (ISO 8601 date format) + + ###### Output ``` Attempting to start task 59b824f0-4b81-453f-b9e0-1c58b97c9fb9 @@ -91,9 +159,28 @@ ems-cmd runasync http://192.168.100.200:6330 -space Default -taskID 59b824f0-4b8 ###### Parameters * `-space` - space name, e.g. `Default` * `-taskID` - task guid. +* `-param:XXX ZZ` - set task parameter `XXX` with value `ZZ`. Task guid can be found in the browser location toolbar. E.g, if you have clicked on the edit task link, your browser location seems to be `http://localhost:6330/default/tasks/edit/59b824f0-4b81-453f-b9e0-1c58b97c9fb9`, where `59b824f0-4b81-453f-b9e0-1c58b97c9fb9` - is a desired value +If you want to pass (or override) parameters that were defined in morph project, add `-param:XXX ZZ` to ems-cmd execution line. +Where `XXX` is a parameter name and `ZZ` is a parameter value. +At least one space between parameter name and parameter value is required. + + +E.g. If you've defined parameter `Timeout` in your morph project, and want to set it to 73 use `-param:Timeout 73`. Pay attention, that parameters are case sensitive. + + +Examples: + + +Set parameter `Rounds` to `10` : `-param:Timeout 73` + +Set parameter `Full name` to `John Smith` : `-param:"Full name" "John Smith"` + +Set parameter `From Date` to the `10th of December 2000` : `-param:"From Date" "2000-12-10"` (ISO 8601 date format) + + ###### Output ``` Attempting to start task 59b824f0-4b81-453f-b9e0-1c58b97c9fb9 diff --git a/src/BusinessLogic/Commands/BaseCommand.cs b/src/BusinessLogic/Commands/BaseCommand.cs index e402bc5..76ac3db 100644 --- a/src/BusinessLogic/Commands/BaseCommand.cs +++ b/src/BusinessLogic/Commands/BaseCommand.cs @@ -1,5 +1,7 @@ using Morph.Server.Sdk.Client; +using Morph.Server.Sdk.Model; using MorphCmd.Interfaces; +using MorphCmd.Models; using System; using System.Collections.Generic; using System.Linq; @@ -31,5 +33,23 @@ protected void RequireParam(Guid? value) } + protected async Task OpenSession(Parameters parameters) + { + // for simplification we just check that the user has pass any password + // in more complex logic, you should call GetSpacesListAsync to retrieve a spaces list and check the isPublic property or the space + // isPublic means that you need to open an anon session, otherwise - open a real session + if (string.IsNullOrWhiteSpace(parameters.Password)) + { + var apiSession = ApiSession.Anonymous(parameters.SpaceName); + return apiSession; + } + else + { + var apiSession = await _apiClient.OpenSessionAsync(parameters.SpaceName, parameters.Password, _cancellationTokenSource.Token); + return apiSession; + } + + } + } } diff --git a/src/BusinessLogic/Commands/BrowseCommand.cs b/src/BusinessLogic/Commands/BrowseCommand.cs deleted file mode 100644 index 402937a..0000000 --- a/src/BusinessLogic/Commands/BrowseCommand.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Morph.Server.Sdk.Client; -using MorphCmd.Interfaces; -using MorphCmd.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MorphCmd.BusinessLogic.Commands -{ - - internal class BrowseCommand : BaseCommand, ICommand - { - public BrowseCommand(IOutputEndpoint output, IInputEndpoint input, IMorphServerApiClient apiClient) : base(output, input, apiClient) - { - - } - - public async Task Execute(Parameters parameters) - { - if (string.IsNullOrWhiteSpace(parameters.Location)) - { - _output.WriteInfo("Browsing the root folder of the space " + parameters.Space); - } - else - { - _output.WriteInfo("Browsing the folder '" + parameters.Location + "' of the space " + parameters.Space); - } - var data = await _apiClient.BrowseSpaceAsync(parameters.Space, parameters.Location, _cancellationTokenSource.Token); - _output.WriteInfo("Space: " + data.SpaceName); - _output.WriteInfo("Free space: " + data.FreeSpaceBytes + " bytes"); - foreach (var folder in data.Folders) - { - _output.WriteInfo(string.Format("{0}{1} {2}", folder.LastModified.ToLocalTime().ToString("MM/dd/yyyy hh:mm:ss tt").PadRight(30), "".PadRight(16), folder.Name)); - } - foreach (var file in data.Files) - { - _output.WriteInfo(string.Format("{0}{1} {2}", file.LastModified.ToLocalTime().ToString("MM/dd/yyyy hh:mm:ss tt").PadRight(30), file.FileSizeBytes.ToString("n0").PadLeft(16), file.Name)); - } - - - _output.WriteInfo("Listing done"); - - } - } -} diff --git a/src/BusinessLogic/Commands/BrowseFilesCommand.cs b/src/BusinessLogic/Commands/BrowseFilesCommand.cs new file mode 100644 index 0000000..9fe8764 --- /dev/null +++ b/src/BusinessLogic/Commands/BrowseFilesCommand.cs @@ -0,0 +1,55 @@ +using Morph.Server.Sdk.Client; +using Morph.Server.Sdk.Model; +using MorphCmd.Interfaces; +using MorphCmd.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MorphCmd.BusinessLogic.Commands +{ + + internal class BrowseFilesCommand : BaseCommand, ICommand + { + public BrowseFilesCommand(IOutputEndpoint output, IInputEndpoint input, IMorphServerApiClient apiClient) : base(output, input, apiClient) + { + + } + + public bool IsApiSessionRequired => true; + + public async Task Execute(Parameters parameters) + { + if (string.IsNullOrWhiteSpace(parameters.Location)) + { + _output.WriteInfo("Browsing the root folder of the space " + parameters.SpaceName); + } + else + { + _output.WriteInfo("Browsing the folder '" + parameters.Location + "' of the space " + parameters.SpaceName); + } + + using (var apiSession = await OpenSession(parameters)) + { + + var data = await _apiClient.BrowseSpaceAsync(apiSession, parameters.Location, _cancellationTokenSource.Token); + _output.WriteInfo("Space: " + data.SpaceName); + _output.WriteInfo("Free space: " + data.FreeSpaceBytes + " bytes"); + foreach (var folder in data.Folders) + { + _output.WriteInfo(string.Format("{0}{1} {2}", folder.LastModified.ToLocalTime().ToString("MM/dd/yyyy hh:mm:ss tt").PadRight(30), "".PadRight(16), folder.Name)); + } + foreach (var file in data.Files) + { + _output.WriteInfo(string.Format("{0}{1} {2}", file.LastModified.ToLocalTime().ToString("MM/dd/yyyy hh:mm:ss tt").PadRight(30), file.FileSizeBytes.ToString("n0").PadLeft(16), file.Name)); + } + + + _output.WriteInfo("Listing done"); + } + + } + } +} diff --git a/src/BusinessLogic/Commands/CommandsFactory.cs b/src/BusinessLogic/Commands/CommandsFactory.cs index f8fb022..c242786 100644 --- a/src/BusinessLogic/Commands/CommandsFactory.cs +++ b/src/BusinessLogic/Commands/CommandsFactory.cs @@ -24,13 +24,17 @@ public static ICommand Construct(Command command, IOutputEndpoint output, IInput case Command.Upload: return new UploadFileCommand(output, input, apiClient); case Command.Browse: - return new BrowseCommand(output, input, apiClient); + return new BrowseFilesCommand(output, input, apiClient); case Command.Del: return new DeleteFileCommand(output, input, apiClient); case Command.Download: return new DownloadFileCommand(output, input, apiClient); case Command.ValidateTasks: return new ValidateTasksCommand(output, input, apiClient); + case Command.ListSpaces: + return new ListSpacesCommand(output, input, apiClient); + case Command.SpaceStatus: + return new SpaceStatusCommand(output, input, apiClient); default: throw new Exception("Command not supported"); } diff --git a/src/BusinessLogic/Commands/DeleteFileCommand.cs b/src/BusinessLogic/Commands/DeleteFileCommand.cs index 4f17373..4cd4ee8 100644 --- a/src/BusinessLogic/Commands/DeleteFileCommand.cs +++ b/src/BusinessLogic/Commands/DeleteFileCommand.cs @@ -1,4 +1,5 @@ using Morph.Server.Sdk.Client; +using Morph.Server.Sdk.Model; using MorphCmd.Exceptions; using MorphCmd.Interfaces; using MorphCmd.Models; @@ -17,6 +18,8 @@ public DeleteFileCommand(IOutputEndpoint output, IInputEndpoint input, IMorphSer } + public bool IsApiSessionRequired => true; + public async Task Execute(Parameters parameters) { if (string.IsNullOrWhiteSpace(parameters.Target)) @@ -24,9 +27,12 @@ public async Task Execute(Parameters parameters) throw new WrongCommandFormatException("Target is required"); } - _output.WriteInfo(string.Format("Deleting file {0} in space {1}...", parameters.Target, parameters.Space ?? "Default")); - await _apiClient.DeleteFileAsync(parameters.Space, parameters.Target, null, _cancellationTokenSource.Token); - _output.WriteInfo("Operation completed"); + using (var apiSession = await OpenSession(parameters)) + { + _output.WriteInfo(string.Format("Deleting file {0} in space {1}...", parameters.Target, apiSession.SpaceName)); + await _apiClient.DeleteFileAsync(apiSession, parameters.Target, null, _cancellationTokenSource.Token); + _output.WriteInfo("Operation completed"); + } } } diff --git a/src/BusinessLogic/Commands/DownloadFileCommand.cs b/src/BusinessLogic/Commands/DownloadFileCommand.cs index 3d1cc0a..e88c608 100644 --- a/src/BusinessLogic/Commands/DownloadFileCommand.cs +++ b/src/BusinessLogic/Commands/DownloadFileCommand.cs @@ -21,6 +21,8 @@ public DownloadFileCommand(IOutputEndpoint output, IInputEndpoint input, IMorphS } + public bool IsApiSessionRequired => true; + public async Task Execute(Parameters parameters) { @@ -28,123 +30,123 @@ public async Task Execute(Parameters parameters) { throw new WrongCommandFormatException("Target is required"); } + + if (string.IsNullOrWhiteSpace(parameters.Source)) { throw new WrongCommandFormatException("Source is required"); } - if (string.IsNullOrWhiteSpace(parameters.Space)) - { - throw new WrongCommandFormatException("Space is required"); - } - + if (!Directory.Exists(parameters.Target)) { throw new Exception(string.Format("Target directory {0} not found", parameters.Target)); } - _output.WriteInfo(string.Format("Downloading file '{0}' from space '{1}' into '{2}'...", parameters.Source, parameters.Space, parameters.Target)); - - ProgressBar progress = new ProgressBar(_output, 40); - _apiClient.FileProgress += (object sender, FileEventArgs e) => + using (var apiSession = await OpenSession(parameters)) { - if (e.State == FileProgressState.Starting) - { - progress.Init(); - } - else if (e.State == FileProgressState.Processing) - { - progress.Report(e.Percent / 100); - } - else if (e.State == FileProgressState.Finishing) + _output.WriteInfo(string.Format("Downloading file '{0}' from space '{1}' into '{2}'...", parameters.Source, apiSession.SpaceName, parameters.Target)); + + ProgressBar progress = new ProgressBar(_output, 40); + _apiClient.FileProgress += (object sender, FileEventArgs e) => { - progress.Dispose(); - progress = null; - } + if (e.State == FileProgressState.Starting) + { + progress.Init(); + } + else if (e.State == FileProgressState.Processing) + { + progress.Report(e.Percent / 100); + } + else if (e.State == FileProgressState.Finishing) + { + progress.Dispose(); + progress = null; + } - }; + }; - var tempFile = Guid.NewGuid().ToString("D") + ".tmp"; - tempFile = Path.Combine(parameters.Target, tempFile); + var tempFile = Guid.NewGuid().ToString("D") + ".tmp"; + tempFile = Path.Combine(parameters.Target, tempFile); - string destFileName = null; - var allowLoading = false; - try - { - using (Stream streamToWriteTo = File.Open(tempFile, FileMode.Create)) + string destFileName = null; + var allowLoading = false; + try { - try + using (Stream streamToWriteTo = File.Open(tempFile, FileMode.Create)) { - await _apiClient.DownloadFileAsync(parameters.Space, parameters.Source, (fileInfo) => + try { - destFileName = Path.Combine(parameters.Target, fileInfo.FileName); + await _apiClient.DownloadFileAsync(apiSession, parameters.Source, (fileInfo) => + { + destFileName = Path.Combine(parameters.Target, fileInfo.FileName); - if (!parameters.YesToAll && File.Exists(destFileName)) - throw new FileExistsException("File already exists"); - allowLoading = true; - return true; + if (!parameters.YesToAll && File.Exists(destFileName)) + throw new FileExistsException("File already exists"); + allowLoading = true; + return true; - }, streamToWriteTo, _cancellationTokenSource.Token); - } - catch (FileExistsException) - { - allowLoading = false; - if (!_output.IsOutputRedirected) + }, streamToWriteTo, _cancellationTokenSource.Token); + } + catch (FileExistsException) { - _output.WriteInfo(string.Format("Target file '{0}' already exists. Would you like to overwrite it? Y/N", destFileName)); - _output.WriteInfo("You may pass /y parameter to overwrite file without any questions"); - var answer = _input.ReadLine(); - if (answer.Trim().ToLowerInvariant().StartsWith("y")) + allowLoading = false; + if (!_output.IsOutputRedirected) { - allowLoading = true; - _output.WriteInfo("File will be overwritten"); + _output.WriteInfo(string.Format("Target file '{0}' already exists. Would you like to overwrite it? Y/N", destFileName)); + _output.WriteInfo("You may pass /y parameter to overwrite file without any questions"); + var answer = _input.ReadLine(); + if (answer.Trim().ToLowerInvariant().StartsWith("y")) + { + allowLoading = true; + _output.WriteInfo("File will be overwritten"); + } + else + { + _output.WriteInfo("Operation canceled"); + throw new CommandFailedException(); + } } else { - _output.WriteInfo("Operation canceled"); - throw new CommandFailedException(); + _output.WriteError("File already exists. To overwrite file use /y flag"); + allowLoading = false; } - } - else - { - _output.WriteError("File already exists. To overwrite file use /y flag"); - allowLoading = false; - } - if (allowLoading) - { - _output.WriteInfo(string.Format("Downloading '{0}' ...", parameters.Source)); - await _apiClient.DownloadFileAsync(parameters.Space, parameters.Source, (fileInfo) => + if (allowLoading) { - return true; - }, streamToWriteTo, _cancellationTokenSource.Token); + _output.WriteInfo(string.Format("Downloading '{0}' ...", parameters.Source)); + await _apiClient.DownloadFileAsync(apiSession, parameters.Source, (fileInfo) => + { + return true; + }, streamToWriteTo, _cancellationTokenSource.Token); + } } - } - - } + } - if (allowLoading) - { - if (File.Exists(destFileName)) - File.Delete(destFileName); - File.Move(tempFile, destFileName); + if (allowLoading) + { - } + if (File.Exists(destFileName)) + File.Delete(destFileName); + File.Move(tempFile, destFileName); - _output.WriteInfo("Operation completed"); + } - } - finally - { - //drop file - if (tempFile != null) - File.Delete(tempFile); + _output.WriteInfo("Operation completed"); - } + } + finally + { + //drop file + if (tempFile != null) + File.Delete(tempFile); + } + } } } } diff --git a/src/BusinessLogic/Commands/ListSpacesCommand.cs b/src/BusinessLogic/Commands/ListSpacesCommand.cs new file mode 100644 index 0000000..ad2be10 --- /dev/null +++ b/src/BusinessLogic/Commands/ListSpacesCommand.cs @@ -0,0 +1,40 @@ +using Morph.Server.Sdk.Client; +using Morph.Server.Sdk.Model; +using MorphCmd.Interfaces; +using MorphCmd.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MorphCmd.BusinessLogic.Commands +{ + + internal class ListSpacesCommand : BaseCommand, ICommand + { + public ListSpacesCommand(IOutputEndpoint output, IInputEndpoint input, IMorphServerApiClient apiClient) : base(output, input, apiClient) + { + + } + + public bool IsApiSessionRequired => false; + + public async Task Execute(Parameters parameters) + { + + + var list = await _apiClient.GetSpacesListAsync(_cancellationTokenSource.Token); + _output.WriteInfo("Available spaces:"); + + foreach (var space in list.Items) + { + _output.WriteInfo(string.Format("{0} {1}", space.IsPublic ? " " : "*", space.SpaceName)); + } + + _output.WriteInfo("Listing done"); + } + + + } +} diff --git a/src/BusinessLogic/Commands/RunTaskAndForgetCommand.cs b/src/BusinessLogic/Commands/RunTaskAndForgetCommand.cs index 2580135..aa2c902 100644 --- a/src/BusinessLogic/Commands/RunTaskAndForgetCommand.cs +++ b/src/BusinessLogic/Commands/RunTaskAndForgetCommand.cs @@ -1,4 +1,5 @@ using Morph.Server.Sdk.Client; +using Morph.Server.Sdk.Model; using MorphCmd.Exceptions; using MorphCmd.Interfaces; using MorphCmd.Models; @@ -17,15 +18,31 @@ public RunTaskAndForgetCommand(IOutputEndpoint output, IInputEndpoint input, IMo } + public bool IsApiSessionRequired => true; + public async Task Execute(Parameters parameters) { if (parameters.TaskId == null) { throw new WrongCommandFormatException("TaskId is required"); } - _output.WriteInfo("Attempting to start task " + parameters.TaskId.Value.ToString("D")); - var info = await _apiClient.StartTaskAsync(parameters.Space, parameters.TaskId.Value, _cancellationTokenSource.Token); - _output.WriteInfo(string.Format("Project '{0}' is running.", info.ProjectName)); + + _output.WriteInfo($"Attempting to start task {parameters.TaskId.Value.ToString("D")} in space '{parameters.SpaceName}'"); + foreach (var parameter in parameters.TaskRunParameters) + { + _output.WriteInfo($"Parameter '{parameter.Name}' = '{parameter.Value}'"); + } + + using (var apiSession = await OpenSession(parameters)) + { + + var info = await _apiClient.StartTaskAsync(apiSession, + parameters.TaskId.Value, + _cancellationTokenSource.Token, + parameters.TaskRunParameters.Select(x => new TaskStringParameter(x.Name, x.Value)).ToArray() + ); + _output.WriteInfo(string.Format("Project '{0}' is running.", info.ProjectName)); + } } } diff --git a/src/BusinessLogic/Commands/RunTaskAndWaitCommand.cs b/src/BusinessLogic/Commands/RunTaskAndWaitCommand.cs index a8e2d0a..b47fd90 100644 --- a/src/BusinessLogic/Commands/RunTaskAndWaitCommand.cs +++ b/src/BusinessLogic/Commands/RunTaskAndWaitCommand.cs @@ -1,4 +1,5 @@ using Morph.Server.Sdk.Client; +using Morph.Server.Sdk.Model; using MorphCmd.Exceptions; using MorphCmd.Interfaces; using MorphCmd.Models; @@ -12,45 +13,59 @@ namespace MorphCmd.BusinessLogic.Commands { internal class RunTaskAndWaitCommand : BaseCommand, ICommand { + + public RunTaskAndWaitCommand(IOutputEndpoint output, IInputEndpoint input, IMorphServerApiClient apiClient) : base(output, input, apiClient) { - + } + public bool IsApiSessionRequired => true; + public async Task Execute(Parameters parameters) - { + { + if (parameters.TaskId == null) { throw new WrongCommandFormatException("TaskId is required"); } - _output.WriteInfo("Attempting to start task " + parameters.TaskId.Value.ToString("D")); - - var status = await _apiClient.GetTaskStatusAsync( parameters.TaskId.Value, _cancellationTokenSource.Token); - if (status.IsRunning) + _output.WriteInfo($"Attempting to start task {parameters.TaskId.Value.ToString("D")} in space '{parameters.SpaceName}'" ); + foreach (var parameter in parameters.TaskRunParameters) { - throw new Exception($"Task {parameters.TaskId.Value.ToString("D")} is already running. Exiting"); + _output.WriteInfo($"Parameter '{parameter.Name}' = '{parameter.Value}'"); } - var info = await _apiClient.StartTaskAsync(parameters.Space, parameters.TaskId.Value, _cancellationTokenSource.Token); - - _output.WriteInfo(string.Format("Project '{0}' is running. Waiting until done.", info.ProjectName)); + using (var apiSession = await OpenSession(parameters)) + { - do - { - await Task.Delay(TimeSpan.FromSeconds(1)); - } - while ((status = await _apiClient.GetTaskStatusAsync(parameters.TaskId.Value, _cancellationTokenSource.Token)).IsRunning); - if (status.TaskState != Morph.Server.Sdk.Model.TaskState.Failed) - { - _output.WriteInfo(string.Format("\nTask {0} completed", parameters.TaskId.Value.ToString("D"))); - } - else - { - _output.WriteInfo(string.Format("\nTask {0} failed", parameters.TaskId.Value.ToString("D"))); - } + var status = await _apiClient.GetTaskStatusAsync(apiSession, parameters.TaskId.Value, _cancellationTokenSource.Token); + if (status.IsRunning) + { + throw new Exception($"Task {parameters.TaskId.Value.ToString("D")} is already running. Exiting"); + } + var info = await _apiClient.StartTaskAsync( + apiSession, + parameters.TaskId.Value, + _cancellationTokenSource.Token, + parameters.TaskRunParameters.Select(x => new TaskStringParameter(x.Name, x.Value)).ToArray()); + _output.WriteInfo(string.Format("Project '{0}' is running. Waiting until done.", info.ProjectName)); + do + { + await Task.Delay(TimeSpan.FromSeconds(1)); + } + while ((status = await _apiClient.GetTaskStatusAsync(apiSession, parameters.TaskId.Value, _cancellationTokenSource.Token)).IsRunning); + if (status.TaskState != TaskState.Failed) + { + _output.WriteInfo(string.Format("\nTask {0} completed", parameters.TaskId.Value.ToString("D"))); + } + else + { + _output.WriteInfo(string.Format("\nTask {0} failed", parameters.TaskId.Value.ToString("D"))); + } + } } } } diff --git a/src/BusinessLogic/Commands/ServerStatusCommand.cs b/src/BusinessLogic/Commands/ServerStatusCommand.cs index 5c1e522..302cc84 100644 --- a/src/BusinessLogic/Commands/ServerStatusCommand.cs +++ b/src/BusinessLogic/Commands/ServerStatusCommand.cs @@ -1,4 +1,5 @@ using Morph.Server.Sdk.Client; +using Morph.Server.Sdk.Model; using MorphCmd.Interfaces; using MorphCmd.Models; using System; @@ -16,6 +17,8 @@ public ServerStatusCommand(IOutputEndpoint output, IInputEndpoint input, IMorphS } + public bool IsApiSessionRequired => false; + public async Task Execute(Parameters parameters) { _output.WriteInfo("Retrieving server status..."); diff --git a/src/BusinessLogic/Commands/SpaceStatusCommand.cs b/src/BusinessLogic/Commands/SpaceStatusCommand.cs new file mode 100644 index 0000000..fb6f68f --- /dev/null +++ b/src/BusinessLogic/Commands/SpaceStatusCommand.cs @@ -0,0 +1,39 @@ +using Morph.Server.Sdk.Client; +using Morph.Server.Sdk.Model; +using MorphCmd.Interfaces; +using MorphCmd.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MorphCmd.BusinessLogic.Commands +{ + + internal class SpaceStatusCommand : BaseCommand, ICommand + { + public SpaceStatusCommand(IOutputEndpoint output, IInputEndpoint input, IMorphServerApiClient apiClient) : base(output, input, apiClient) + { + + } + + public bool IsApiSessionRequired => true; + + public async Task Execute(Parameters parameters) + { + _output.WriteInfo($"Checking space {parameters.SpaceName} status..."); + + using (var apiSession = await OpenSession(parameters)) + { + var data = await _apiClient.GetSpaceStatusAsync(apiSession, _cancellationTokenSource.Token); + _output.WriteInfo("Space: " + data.SpaceName); + _output.WriteInfo("IsPublic: " + data.IsPublic); + _output.WriteInfo("Permissions: " + string.Join(", ", data.SpacePermissions)); + + _output.WriteInfo("done"); + } + + } + } +} diff --git a/src/BusinessLogic/Commands/UploadFileCommand.cs b/src/BusinessLogic/Commands/UploadFileCommand.cs index d08f4dd..ec3b46f 100644 --- a/src/BusinessLogic/Commands/UploadFileCommand.cs +++ b/src/BusinessLogic/Commands/UploadFileCommand.cs @@ -21,6 +21,8 @@ public UploadFileCommand(IOutputEndpoint output, IInputEndpoint input, IMorphSer } + public bool IsApiSessionRequired => true; + public async Task Execute(Parameters parameters) { if (string.IsNullOrWhiteSpace(parameters.Source)) @@ -28,13 +30,14 @@ public async Task Execute(Parameters parameters) throw new WrongCommandFormatException("Source is required"); } + if (string.IsNullOrWhiteSpace(parameters.Target)) { - _output.WriteInfo(string.Format("Uploading file '{0}' to the root folder of space '{1}'...", parameters.Source, parameters.Space ?? "Default")); + _output.WriteInfo(string.Format("Uploading file '{0}' to the root folder of space '{1}'...", parameters.Source, parameters.SpaceName)); } else { - _output.WriteInfo(string.Format("Uploading file '{0}' to folder '{1}' of space '{2}'...", parameters.Source, parameters.Target, parameters.Space ?? "Default")); + _output.WriteInfo(string.Format("Uploading file '{0}' to folder '{1}' of space '{2}'...", parameters.Source, parameters.Target, parameters.SpaceName)); } if (!File.Exists(parameters.Source)) @@ -64,53 +67,55 @@ public async Task Execute(Parameters parameters) }; - var browsing = await _apiClient.BrowseSpaceAsync(parameters.Space, parameters.Target, _cancellationTokenSource.Token); - if (!browsing.CanUploadFiles) + using (var apiSession = await OpenSession(parameters)) { - throw new Exception("Uploading to this space is disabled"); - } + var browsing = await _apiClient.BrowseSpaceAsync(apiSession, parameters.Target, _cancellationTokenSource.Token); + if (!browsing.CanUploadFiles) + { + throw new Exception("Uploading to this space is disabled"); + } - if (parameters.YesToAll) - { - // don't care if file exists. - _output.WriteInfo(string.Format("YES key was passed. File will be overridden if it already exists")); - await _apiClient.UploadFileAsync(parameters.Space, parameters.Source, parameters.Target, _cancellationTokenSource.Token, overwriteFileifExists: true); - } - else - { - - var fileExists = browsing.FileExists(Path.GetFileName(parameters.Source)); - if (fileExists) + if (parameters.YesToAll) { - if (_output.IsOutputRedirected) - { - _output.WriteError(string.Format("Unable to upload file '{0}' due to file already exists. Use /y to override it ", parameters.Target)); - } - else + // don't care if file exists. + _output.WriteInfo(string.Format("YES key was passed. File will be overridden if it already exists")); + await _apiClient.UploadFileAsync(apiSession, parameters.Source, parameters.Target, _cancellationTokenSource.Token, overwriteFileifExists: true); + } + else + { + + var fileExists = browsing.FileExists(Path.GetFileName(parameters.Source)); + if (fileExists) { - _output.WriteInfo("Uploading file already exists. Would you like to override it? Y/N"); - _output.WriteInfo("You may pass /y parameter to override file without any questions"); - var answer = _input.ReadLine(); - if (answer.Trim().ToLowerInvariant().StartsWith("y")) + if (_output.IsOutputRedirected) { - _output.WriteInfo("Uploading file..."); - await _apiClient.UploadFileAsync(parameters.Space, parameters.Source, parameters.Target, _cancellationTokenSource.Token, overwriteFileifExists: true); - _output.WriteInfo("Operation complete"); + _output.WriteError(string.Format("Unable to upload file '{0}' due to file already exists. Use /y to override it ", parameters.Target)); } else { - _output.WriteInfo("Operation canceled"); + _output.WriteInfo("Uploading file already exists. Would you like to override it? Y/N"); + _output.WriteInfo("You may pass /y parameter to override file without any questions"); + var answer = _input.ReadLine(); + if (answer.Trim().ToLowerInvariant().StartsWith("y")) + { + _output.WriteInfo("Uploading file..."); + await _apiClient.UploadFileAsync(apiSession, parameters.Source, parameters.Target, _cancellationTokenSource.Token, overwriteFileifExists: true); + _output.WriteInfo("Operation complete"); + } + else + { + _output.WriteInfo("Operation canceled"); + } } } - } - else - { - await _apiClient.UploadFileAsync(parameters.Space, parameters.Source, parameters.Target, _cancellationTokenSource.Token, overwriteFileifExists: false); - } + else + { + await _apiClient.UploadFileAsync(apiSession, parameters.Source, parameters.Target, _cancellationTokenSource.Token, overwriteFileifExists: false); + } + } } - } } } diff --git a/src/BusinessLogic/Commands/ValidateTasksCommand.cs b/src/BusinessLogic/Commands/ValidateTasksCommand.cs index e7d3181..dfb036f 100644 --- a/src/BusinessLogic/Commands/ValidateTasksCommand.cs +++ b/src/BusinessLogic/Commands/ValidateTasksCommand.cs @@ -1,5 +1,6 @@ using Morph.Server.Sdk.Client; using Morph.Server.Sdk.Exceptions; +using Morph.Server.Sdk.Model; using MorphCmd.Exceptions; using MorphCmd.Interfaces; using MorphCmd.Models; @@ -18,46 +19,46 @@ public ValidateTasksCommand(IOutputEndpoint output, IInputEndpoint input, IMorph } + public bool IsApiSessionRequired => true; + public async Task Execute(Parameters parameters) { if (string.IsNullOrWhiteSpace(parameters.Location)) { throw new WrongCommandFormatException("Location is required"); } - if (string.IsNullOrWhiteSpace(parameters.Space)) - { - throw new WrongCommandFormatException("Space is required"); - } - try + using (var apiSession = await OpenSession(parameters)) { - _output.WriteInfo("Validating tasks for the project '" + parameters.Location + "'"); - var result = await _apiClient.ValidateTasksAsync(parameters.Space, parameters.Location, _cancellationTokenSource.Token); - - if (result.FailedTasks.Count == 0) + try { - _output.WriteInfo("All tasks are valid"); - } - else - { - _output.WriteError(result.Message); - foreach (var item in result.FailedTasks) + _output.WriteInfo("Validating tasks for the project '" + parameters.Location + "'"); + var result = await _apiClient.ValidateTasksAsync(apiSession, parameters.Location, _cancellationTokenSource.Token); + + if (result.FailedTasks.Count == 0) { - _output.WriteInfo(item.TaskId + ": " + item.Message + "@" + item.TaskApiUrl); + _output.WriteInfo("All tasks are valid"); + } + else + { + _output.WriteError(result.Message); + foreach (var item in result.FailedTasks) + { + _output.WriteInfo(item.TaskId + ": " + item.Message + "@" + item.TaskApiUrl); + } } } - } - catch (MorphApiBadArgumentException ba) - { - _output.WriteError(ba.Message); - foreach (var e in ba.Details) + catch (MorphApiBadArgumentException ba) { - _output.WriteInfo(e.Field + ": " + e.Message); + _output.WriteError(ba.Message); + foreach (var e in ba.Details) + { + _output.WriteInfo(e.Field + ": " + e.Message); + } + throw new CommandFailedException(); } - throw new CommandFailedException(); } - } } diff --git a/src/BusinessLogic/CommandsHandler.cs b/src/BusinessLogic/CommandsHandler.cs index 720e82e..c6f4cf5 100644 --- a/src/BusinessLogic/CommandsHandler.cs +++ b/src/BusinessLogic/CommandsHandler.cs @@ -16,7 +16,7 @@ namespace MorphCmd.BusinessLogic { - + internal class CommandsHandler { private readonly IOutputEndpoint _output; @@ -34,21 +34,33 @@ public CommandsHandler(IOutputEndpoint output, IInputEndpoint input, IMorphServe } public async Task Handle(Parameters parameters) - { + { + ApiSession apiSession = null; var cmd = CommandsFactory.Construct(parameters.Command, _output, _input, _apiClient); try { + // if none space name was set - force to use 'default' space + if (string.IsNullOrWhiteSpace(parameters.SpaceName)) + { + parameters.SpaceName = "default"; + } + await cmd.Execute(parameters); } catch (WrongCommandFormatException wrg) { _output.WriteError("Wrong command format"); - + RunUsageSamples.WriteCommadUsage(parameters.Command, _output); throw; } + finally + { + apiSession?.Dispose(); + } + } } diff --git a/src/BusinessLogic/ParametersHelper.cs b/src/BusinessLogic/ParametersHelper.cs index 13e85ab..798d761 100644 --- a/src/BusinessLogic/ParametersHelper.cs +++ b/src/BusinessLogic/ParametersHelper.cs @@ -15,7 +15,9 @@ public static Parameters ExtractParameters(string command, string host, Dictiona Parameters parameters = new Parameters(); parameters.Host = host; if (paramsDict.ContainsKey("space")) - parameters.Space = paramsDict["space"]; + parameters.SpaceName = paramsDict["space"]; + if (paramsDict.ContainsKey("password")) + parameters.Password = paramsDict["password"]; if (paramsDict.ContainsKey("source")) parameters.Source = paramsDict["source"]; if (paramsDict.ContainsKey("destination")) @@ -28,6 +30,26 @@ public static Parameters ExtractParameters(string command, string host, Dictiona if (paramsDict.ContainsKey("y")) parameters.YesToAll = true; + + var runTaskParameters = paramsDict.Keys.Where(x => x.StartsWith("param:")).ToArray(); + foreach(var p in runTaskParameters) + { + var split = p.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + var wrongFormat = split.Length != 2; + string name = null; + if (!wrongFormat) + { + name = split[1]; + } + + if (string.IsNullOrWhiteSpace(name) || wrongFormat) + { + throw new Exception("Malformed task parameter value. Expected something like -param:SomeDate 2015-02-11 -param:\"File name\" \"C:\\My Documents\\input.csv\"" ); + } + parameters.TaskRunParameters.Add( new TaskRunParameter(name, paramsDict[p])); + } + + if (paramsDict.ContainsKey("taskid")) { Guid guid; diff --git a/src/Interfaces/ICommand.cs b/src/Interfaces/ICommand.cs index e633910..77336d1 100644 --- a/src/Interfaces/ICommand.cs +++ b/src/Interfaces/ICommand.cs @@ -1,4 +1,5 @@ -using MorphCmd.Models; +using Morph.Server.Sdk.Model; +using MorphCmd.Models; using System; using System.Collections.Generic; using System.Linq; @@ -11,5 +12,6 @@ namespace MorphCmd.Interfaces internal interface ICommand { Task Execute(Parameters parameters); + } } diff --git a/src/Models/Command.cs b/src/Models/Command.cs index 32f54a7..156c7e7 100644 --- a/src/Models/Command.cs +++ b/src/Models/Command.cs @@ -15,7 +15,9 @@ public enum Command Browse, Download, Del, - ValidateTasks + ValidateTasks, + ListSpaces, + SpaceStatus } diff --git a/src/Models/Parameters.cs b/src/Models/Parameters.cs index 64fd97e..caf2655 100644 --- a/src/Models/Parameters.cs +++ b/src/Models/Parameters.cs @@ -1,4 +1,5 @@ -using MorphCmd.BusinessLogic; +using Morph.Server.Sdk.Model; +using MorphCmd.BusinessLogic; using System; using System.Collections.Generic; using System.Linq; @@ -10,7 +11,7 @@ namespace MorphCmd.Models public class Parameters { public Command Command { get; set; } - public string Space { get; set; } + public string SpaceName { get; set; } public string Source { get; set; } public string Target { get; set; } public string Location { get; set; } @@ -18,5 +19,28 @@ public class Parameters public Guid? TaskId { get; set; } public bool YesToAll { get; set; } public string Host { get; set; } + public string Password { get; set; } + public List TaskRunParameters { get; set; } + public Parameters() + { + TaskRunParameters = new List(); + } + } + + public class TaskRunParameter + { + public string Name{ get; set; } + public string Value { get; set; } + + public TaskRunParameter(string name, string value) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("message", nameof(name)); + } + + this.Name = name; + this.Value = value; + } } } diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs index 4dc0bd5..88e2cee 100644 --- a/src/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -8,7 +8,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ems-cmd")] -[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyCopyright("Copyright © EasyMorph Inc. 2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -18,5 +18,5 @@ [assembly: Guid("6e61eac0-2957-415d-89af-ea675fc244cb")] -[assembly: AssemblyVersion("1.2.0.0")] -[assembly: AssemblyFileVersion("1.2.0.0")] +[assembly: AssemblyVersion("1.3.0.0")] +[assembly: AssemblyFileVersion("1.3.0.0")] diff --git a/src/Utils/CmdParametersHelper.cs b/src/Utils/CmdParametersHelper.cs index dbf0a8d..6e10939 100644 --- a/src/Utils/CmdParametersHelper.cs +++ b/src/Utils/CmdParametersHelper.cs @@ -21,6 +21,14 @@ public static Dictionary ParseParams(string[] args) if (param.StartsWith("-")) { var paramName = param.Substring(1).Trim().ToLowerInvariant(); + if (paramsQ.Count == 0) + throw new Exception("Wrong formatted value for paramter " + paramName); + + if (paramName.StartsWith("param:")) + { + paramName = "param:" + param.Substring(1 + "param:".Length).Trim(); + } + var paramValue = paramsQ.Dequeue(); result[paramName] = paramValue; } @@ -34,7 +42,7 @@ public static Dictionary ParseParams(string[] args) } catch (Exception e) { - throw new Exception("Unable to parse command parameters:" + e.Message); + throw new Exception("Unable to parse command parameters: " + e.Message); } } } diff --git a/src/ems-cmd.csproj b/src/ems-cmd.csproj index b7265fb..0a459d6 100644 --- a/src/ems-cmd.csproj +++ b/src/ems-cmd.csproj @@ -70,7 +70,9 @@ - + + +