diff --git a/src/Client/DataTransferUtility.cs b/src/Client/DataTransferUtility.cs
new file mode 100644
index 0000000..a7ab2a0
--- /dev/null
+++ b/src/Client/DataTransferUtility.cs
@@ -0,0 +1,177 @@
+using Morph.Server.Sdk.Model;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Morph.Server.Sdk.Client
+{
+ ///
+ /// Transfer file from/to server to/from local file
+ ///
+ public class DataTransferUtility : IDataTransferUtility
+ {
+ private int BufferSize { get; set; } = 81920;
+ private readonly IMorphServerApiClient _morphServerApiClient;
+ private readonly ApiSession _apiSession;
+
+ public DataTransferUtility(IMorphServerApiClient morphServerApiClient, ApiSession apiSession)
+ {
+ this._morphServerApiClient = morphServerApiClient ?? throw new ArgumentNullException(nameof(morphServerApiClient));
+ this._apiSession = apiSession ?? throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ public async Task SpaceUploadFileAsync(string localFilePath, string serverFolder, CancellationToken cancellationToken, bool overwriteExistingFile = false)
+ {
+ if (!File.Exists(localFilePath))
+ {
+ throw new FileNotFoundException(string.Format("File '{0}' not found", localFilePath));
+ }
+ var fileSize = new FileInfo(localFilePath).Length;
+ var fileName = Path.GetFileName(localFilePath);
+ using (var fsSource = new FileStream(localFilePath, FileMode.Open, FileAccess.Read))
+ {
+ var request = new SpaceUploadDataStreamRequest
+ {
+ DataStream = fsSource,
+ FileName = fileName,
+ FileSize = fileSize,
+ OverwriteExistingFile = overwriteExistingFile,
+ ServerFolder = serverFolder
+ };
+ await _morphServerApiClient.SpaceUploadDataStreamAsync(_apiSession, request, cancellationToken);
+ return;
+ }
+ }
+
+
+
+
+ public async Task SpaceDownloadFileIntoFileAsync(string remoteFilePath, string targetLocalFilePath, CancellationToken cancellationToken, bool overwriteExistingFile = false)
+ {
+ if (remoteFilePath == null)
+ {
+ throw new ArgumentNullException(nameof(remoteFilePath));
+ }
+
+ if (targetLocalFilePath == null)
+ {
+ throw new ArgumentNullException(nameof(targetLocalFilePath));
+ }
+
+
+ var localFolder = Path.GetDirectoryName(targetLocalFilePath);
+ var tempFile = Path.Combine(localFolder, Guid.NewGuid().ToString("D") + ".emtmp");
+
+ if (!overwriteExistingFile && File.Exists(targetLocalFilePath))
+ {
+ throw new Exception($"Destination file '{targetLocalFilePath}' already exists.");
+ }
+
+
+ try
+ {
+ using (Stream tempFileStream = File.Open(tempFile, FileMode.Create))
+ {
+ using (var serverStreamingData = await _morphServerApiClient.SpaceOpenStreamingDataAsync(_apiSession, remoteFilePath, cancellationToken))
+ {
+ await serverStreamingData.Stream.CopyToAsync(tempFileStream, BufferSize, cancellationToken);
+ }
+ }
+
+ if (File.Exists(targetLocalFilePath))
+ {
+ File.Delete(targetLocalFilePath);
+ }
+ File.Move(tempFile, targetLocalFilePath);
+
+ }
+ finally
+ {
+ //drop file
+ if (tempFile != null && File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
+
+ }
+ }
+
+ public async Task SpaceDownloadFileIntoFolderAsync(string remoteFilePath, string targetLocalFolder, CancellationToken cancellationToken, bool overwriteExistingFile = false)
+ {
+ if (remoteFilePath == null)
+ {
+ throw new ArgumentNullException(nameof(remoteFilePath));
+ }
+
+ if (targetLocalFolder == null)
+ {
+ throw new ArgumentNullException(nameof(targetLocalFolder));
+ }
+
+ string destFileName = null;
+ var tempFile = Path.Combine(targetLocalFolder, Guid.NewGuid().ToString("D") + ".emtmp");
+ try
+ {
+ using (Stream tempFileStream = File.Open(tempFile, FileMode.Create))
+ {
+
+ using (var serverStreamingData = await _morphServerApiClient.SpaceOpenStreamingDataAsync(_apiSession, remoteFilePath, cancellationToken))
+ {
+ destFileName = Path.Combine(targetLocalFolder, serverStreamingData.FileName);
+
+ if (!overwriteExistingFile && File.Exists(destFileName))
+ {
+ throw new Exception($"Destination file '{destFileName}' already exists.");
+ }
+
+ await serverStreamingData.Stream.CopyToAsync(tempFileStream, BufferSize, cancellationToken);
+ }
+ }
+
+ if (File.Exists(destFileName))
+ {
+ File.Delete(destFileName);
+ }
+ File.Move(tempFile, destFileName);
+
+ }
+ finally
+ {
+ //drop file
+ if (tempFile != null && File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
+
+ }
+
+ }
+
+ public async Task SpaceUploadFileAsync(string localFilePath, string serverFolder, string destFileName, CancellationToken cancellationToken, bool overwriteExistingFile = false)
+ {
+ if (!File.Exists(localFilePath))
+ {
+ throw new FileNotFoundException(string.Format("File '{0}' not found", localFilePath));
+ }
+ var fileSize = new FileInfo(localFilePath).Length;
+
+ using (var fsSource = new FileStream(localFilePath, FileMode.Open, FileAccess.Read))
+ {
+ var request = new SpaceUploadDataStreamRequest
+ {
+ DataStream = fsSource,
+ FileName = destFileName,
+ FileSize = fileSize,
+ OverwriteExistingFile = overwriteExistingFile,
+ ServerFolder = serverFolder
+ };
+ await _morphServerApiClient.SpaceUploadDataStreamAsync(_apiSession, request, cancellationToken);
+ return;
+ }
+ }
+ }
+
+}
+
+
diff --git a/src/Client/IDataTransferUtility.cs b/src/Client/IDataTransferUtility.cs
new file mode 100644
index 0000000..d198ade
--- /dev/null
+++ b/src/Client/IDataTransferUtility.cs
@@ -0,0 +1,14 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Morph.Server.Sdk.Client
+{
+ public interface IDataTransferUtility
+ {
+ Task SpaceUploadFileAsync(string localFilePath, string serverFolder, CancellationToken cancellationToken, bool overwriteExistingFile = false);
+ Task SpaceUploadFileAsync(string localFilePath, string serverFolder, string destFileName, CancellationToken cancellationToken, bool overwriteExistingFile = false);
+ Task SpaceDownloadFileIntoFileAsync(string remoteFilePath, string targetLocalFilePath, CancellationToken cancellationToken, bool overwriteExistingFile = false);
+ Task SpaceDownloadFileIntoFolderAsync(string remoteFilePath, string targetLocalFolder, CancellationToken cancellationToken, bool overwriteExistingFile = false);
+ }
+
+}
\ No newline at end of file
diff --git a/src/Client/ILowLevelApiClient.cs b/src/Client/ILowLevelApiClient.cs
new file mode 100644
index 0000000..5bdadb3
--- /dev/null
+++ b/src/Client/ILowLevelApiClient.cs
@@ -0,0 +1,63 @@
+using Morph.Server.Sdk.Dto;
+using Morph.Server.Sdk.Model;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Morph.Server.Sdk.Dto.Commands;
+using System.Collections.Generic;
+using Morph.Server.Sdk.Model.InternalModels;
+using Morph.Server.Sdk.Events;
+
+namespace Morph.Server.Sdk.Client
+{
+ internal interface ILowLevelApiClient: IDisposable
+ {
+ IRestClient RestClient { get; }
+
+ // TASKS
+ Task> GetTaskStatusAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken);
+ Task> GetTasksListAsync(ApiSession apiSession, CancellationToken cancellationToken);
+ Task> GetTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken);
+ Task> TaskChangeModeAsync(ApiSession apiSession, Guid taskId, SpaceTaskChangeModeRequestDto requestDto, CancellationToken cancellationToken);
+
+ // RUN-STOP Task
+ Task> GetRunningTaskStatusAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken);
+ Task> StartTaskAsync(ApiSession apiSession, Guid taskId, TaskStartRequestDto taskStartRequestDto, CancellationToken cancellationToken);
+ Task> StopTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken);
+
+ // Tasks validation
+ Task> ValidateTasksAsync(ApiSession apiSession, ValidateTasksRequestDto validateTasksRequestDto, CancellationToken cancellationToken);
+
+
+ // Auth and sessions
+ Task> AuthLogoutAsync(ApiSession apiSession, CancellationToken cancellationToken);
+ Task> AuthLoginPasswordAsync(LoginRequestDto loginRequestDto, CancellationToken cancellationToken);
+ Task> AuthGenerateNonce(CancellationToken cancellationToken);
+
+
+
+ // Server interaction
+ Task> ServerGetStatusAsync(CancellationToken cancellationToken);
+
+
+ // spaces
+
+ Task> SpacesGetListAsync(CancellationToken cancellationToken);
+ Task> SpacesGetSpaceStatusAsync(ApiSession apiSession, string spaceName, CancellationToken cancellationToken);
+
+ // WEB FILES
+ Task> WebFilesBrowseSpaceAsync(ApiSession apiSession, string folderPath, CancellationToken cancellationToken);
+ Task> WebFileExistsAsync(ApiSession apiSession, string serverFilePath, CancellationToken cancellationToken);
+ Task> WebFilesDeleteFileAsync(ApiSession apiSession, string serverFilePath, CancellationToken cancellationToken);
+ Task> WebFilesDownloadFileAsync(ApiSession apiSession, string serverFilePath, Action onReceiveProgress, CancellationToken cancellationToken);
+ Task> WebFilesPutFileStreamAsync(ApiSession apiSession, string serverFolder, SendFileStreamData sendFileStreamData, Action onSendProgress, CancellationToken cancellationToken);
+ Task> WebFilesPostFileStreamAsync(ApiSession apiSession, string serverFolder, SendFileStreamData sendFileStreamData, Action onSendProgress, CancellationToken cancellationToken);
+
+ Task> WebFilesOpenContiniousPostStreamAsync(ApiSession apiSession, string serverFolder, string fileName, CancellationToken cancellationToken);
+ Task> WebFilesOpenContiniousPutStreamAsync(ApiSession apiSession, string serverFolder, string fileName, CancellationToken cancellationToken);
+
+ }
+}
+
+
+
diff --git a/src/Client/IMorphServerApiClient.cs b/src/Client/IMorphServerApiClient.cs
index b2392ac..bdb4b46 100644
--- a/src/Client/IMorphServerApiClient.cs
+++ b/src/Client/IMorphServerApiClient.cs
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Net.Http;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Morph.Server.Sdk.Events;
@@ -9,29 +12,49 @@
namespace Morph.Server.Sdk.Client
{
- public interface IMorphServerApiClient
+
+ public interface IHasConfig
{
- event EventHandler FileProgress;
+ IClientConfiguration Config { get; }
+ }
- Task BrowseSpaceAsync(ApiSession apiSession, string folderPath, CancellationToken cancellationToken);
+ internal interface ICanCloseSession: IHasConfig, IDisposable
+ {
Task CloseSessionAsync(ApiSession apiSession, CancellationToken cancellationToken);
- Task DeleteFileAsync(ApiSession apiSession, string serverFolder, string fileName, CancellationToken cancellationToken);
- Task DownloadFileAsync(ApiSession apiSession, string remoteFilePath, Func handleFile, Stream streamToWriteTo, CancellationToken cancellationToken);
- Task DownloadFileAsync(ApiSession apiSession, string remoteFilePath, Stream streamToWriteTo, CancellationToken cancellationToken);
- Task FileExistsAsync(ApiSession apiSession, string serverFolder, string fileName, CancellationToken cancellationToken);
+
+ }
+
+ public interface IMorphServerApiClient: IHasConfig, IDisposable
+ {
+ event EventHandler OnDataDownloadProgress;
+ event EventHandler OnDataUploadProgress;
+
+
+
Task GetServerStatusAsync(CancellationToken cancellationToken);
Task GetTaskStatusAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken);
Task OpenSessionAsync(OpenSessionRequest openSessionRequest, CancellationToken cancellationToken);
- Task StartTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken, IEnumerable taskParameters = null);
+ Task StartTaskAsync(ApiSession apiSession, StartTaskRequest startTaskRequest, CancellationToken cancellationToken);
Task StopTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken);
- Task UploadFileAsync(ApiSession apiSession, Stream inputStream, string fileName, long fileSize, string destFolderPath, CancellationToken cancellationToken, bool overwriteFileifExists = false);
- Task UploadFileAsync(ApiSession apiSession, string localFilePath, string destFolderPath, string destFileName, CancellationToken cancellationToken, bool overwriteFileifExists = false);
- Task UploadFileAsync(ApiSession apiSession, string localFilePath, string destFolderPath, CancellationToken cancellationToken, bool overwriteFileifExists = false);
+
+
+ Task TaskChangeModeAsync(ApiSession apiSession, Guid taskId, TaskChangeModeRequest taskChangeModeRequest, CancellationToken cancellationToken);
Task ValidateTasksAsync(ApiSession apiSession, string projectPath, CancellationToken cancellationToken);
Task GetSpacesListAsync(CancellationToken cancellationToken);
Task GetSpaceStatusAsync(ApiSession apiSession, CancellationToken cancellationToken);
Task GetTasksListAsync(ApiSession apiSession, CancellationToken cancellationToken);
Task GetTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken);
+
+ Task SpaceBrowseAsync(ApiSession apiSession, string folderPath, CancellationToken cancellationToken);
+ Task SpaceDeleteFileAsync(ApiSession apiSession, string remoteFilePath, CancellationToken cancellationToken);
+ Task SpaceFileExistsAsync(ApiSession apiSession, string remoteFilePath, CancellationToken cancellationToken);
+
+ Task SpaceOpenStreamingDataAsync(ApiSession apiSession, string remoteFilePath, CancellationToken cancellationToken);
+ Task SpaceOpenDataStreamAsync(ApiSession apiSession, string remoteFilePath, CancellationToken cancellationToken);
+
+ Task SpaceUploadDataStreamAsync(ApiSession apiSession, SpaceUploadDataStreamRequest spaceUploadFileRequest, CancellationToken cancellationToken);
+ Task SpaceUploadContiniousStreamingAsync(ApiSession apiSession, SpaceUploadContiniousStreamRequest continiousStreamRequest, CancellationToken cancellationToken);
+
}
}
\ No newline at end of file
diff --git a/src/Client/IRestClient.cs b/src/Client/IRestClient.cs
new file mode 100644
index 0000000..e854894
--- /dev/null
+++ b/src/Client/IRestClient.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Specialized;
+using Morph.Server.Sdk.Model.InternalModels;
+using Morph.Server.Sdk.Events;
+using Morph.Server.Sdk.Model;
+
+namespace Morph.Server.Sdk.Client
+{
+ public interface IRestClient : IDisposable
+ {
+ HttpClient HttpClient { get; set; }
+ Task> GetAsync(string url, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new();
+ Task> HeadAsync(string url, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new();
+ Task> PostAsync(string url, TModel model, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new();
+ Task> PutAsync(string url, TModel model, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new();
+ Task> DeleteAsync(string url, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new();
+ Task> PutFileStreamAsync(string url, SendFileStreamData sendFileStreamData, NameValueCollection urlParameters, HeadersCollection headersCollection, Action onSendProgress, CancellationToken cancellationToken)
+ where TResult : new();
+ Task> PostFileStreamAsync(string url, SendFileStreamData sendFileStreamData, NameValueCollection urlParameters, HeadersCollection headersCollection, Action onSendProgress, CancellationToken cancellationToken)
+ where TResult : new();
+
+ Task> RetrieveFileGetAsync(string url, NameValueCollection urlParameters, HeadersCollection headersCollection, Action onReceiveProgress, CancellationToken cancellationToken);
+
+
+ Task> PushContiniousStreamingDataAsync(
+ HttpMethod httpMethod, string path, ContiniousStreamingRequest startContiniousStreamingRequest, NameValueCollection urlParameters, HeadersCollection headersCollection,
+ CancellationToken cancellationToken)
+ where TResult : new();
+
+
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/src/Client/LowLevelApiClient.cs b/src/Client/LowLevelApiClient.cs
new file mode 100644
index 0000000..1949a4e
--- /dev/null
+++ b/src/Client/LowLevelApiClient.cs
@@ -0,0 +1,312 @@
+using Morph.Server.Sdk.Dto;
+using Morph.Server.Sdk.Model;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Morph.Server.Sdk.Dto.Commands;
+using Morph.Server.Sdk.Mappers;
+using System.Linq;
+using System.Collections.Generic;
+using System.IO;
+using Morph.Server.Sdk.Exceptions;
+using Morph.Server.Sdk.Model.InternalModels;
+using Morph.Server.Sdk.Helper;
+using Morph.Server.Sdk.Events;
+using System.Net.Http;
+
+namespace Morph.Server.Sdk.Client
+{
+
+ internal class LowLevelApiClient : ILowLevelApiClient
+ {
+ private readonly IRestClient apiClient;
+
+ public IRestClient RestClient => apiClient;
+
+ public LowLevelApiClient(IRestClient apiClient)
+ {
+ this.apiClient = apiClient;
+ }
+
+ public Task> AuthLogoutAsync(ApiSession apiSession, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+ var url = "auth/logout";
+ return apiClient.PostAsync(url, null, null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+ public Task> GetRunningTaskStatusAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "runningtasks", taskId.ToString("D"));
+ return apiClient.GetAsync(url, null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+ public Task> SpacesGetListAsync(CancellationToken cancellationToken)
+ {
+ var url = "spaces/list";
+ return apiClient.GetAsync(url, null, new HeadersCollection(), cancellationToken);
+
+ }
+
+ public Task> GetTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ var url = UrlHelper.JoinUrl("space", apiSession.SpaceName, "tasks", taskId.ToString("D"));
+ return apiClient.GetAsync(url, null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+ public Task> GetTasksListAsync(ApiSession apiSession, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ var url = UrlHelper.JoinUrl("space", apiSession.SpaceName, "tasks");
+ return apiClient.GetAsync(url, null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+
+ public Task> GetTaskStatusAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "tasks", taskId.ToString("D"));
+ return apiClient.GetAsync(url, null, apiSession.ToHeadersCollection(), cancellationToken);
+
+ }
+
+ public Task> TaskChangeModeAsync(ApiSession apiSession, Guid taskId, SpaceTaskChangeModeRequestDto requestDto, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "tasks", taskId.ToString("D"), "changeMode");
+
+ return apiClient.PostAsync(url, requestDto, null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+ public Task> ServerGetStatusAsync(CancellationToken cancellationToken)
+ {
+ var url = "server/status";
+ return apiClient.GetAsync(url, null, new HeadersCollection(), cancellationToken);
+ }
+
+ public Task> StartTaskAsync(ApiSession apiSession, Guid taskId, TaskStartRequestDto taskStartRequestDto, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "runningtasks", taskId.ToString("D"), "payload");
+
+ return apiClient.PostAsync(url, taskStartRequestDto, null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+ public Task> StopTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "runningtasks", taskId.ToString("D"));
+ return apiClient.DeleteAsync(url, null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+ public Task> ValidateTasksAsync(ApiSession apiSession, ValidateTasksRequestDto validateTasksRequestDto, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ var spaceName = apiSession.SpaceName;
+ var url = "commands/validatetasks";
+
+ return apiClient.PostAsync(url, validateTasksRequestDto, null, apiSession.ToHeadersCollection(), cancellationToken);
+
+ }
+
+ public Task> SpacesGetSpaceStatusAsync(ApiSession apiSession, string spaceName, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ if (spaceName == null)
+ {
+ throw new ArgumentNullException(nameof(spaceName));
+ }
+
+ spaceName = spaceName.Trim();
+ var url = UrlHelper.JoinUrl("spaces", spaceName, "status");
+
+ return apiClient.GetAsync(url, null, apiSession.ToHeadersCollection(), cancellationToken);
+
+ }
+
+ public Task> WebFilesBrowseSpaceAsync(ApiSession apiSession, string folderPath, CancellationToken cancellationToken)
+ {
+ var spaceName = apiSession.SpaceName;
+
+ var url = UrlHelper.JoinUrl("space", spaceName, "browse", folderPath);
+ return apiClient.GetAsync(url, null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+ public Task> WebFilesDeleteFileAsync(ApiSession apiSession, string serverFilePath, CancellationToken cancellationToken)
+ {
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "files", serverFilePath);
+
+ return apiClient.DeleteAsync(url, null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+ public Task> AuthLoginPasswordAsync(LoginRequestDto loginRequestDto, CancellationToken cancellationToken)
+ {
+ var url = "auth/login";
+ return apiClient.PostAsync(url, loginRequestDto, null, new HeadersCollection(), cancellationToken);
+ }
+
+ public Task> AuthGenerateNonce(CancellationToken cancellationToken)
+ {
+ var url = "auth/nonce";
+ return apiClient.PostAsync(url, new GenerateNonceRequestDto(), null, new HeadersCollection(), cancellationToken);
+ }
+
+ public void Dispose()
+ {
+ apiClient.Dispose();
+ }
+
+ public Task> WebFilesDownloadFileAsync(ApiSession apiSession, string serverFilePath, Action onReceiveProgress, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "files", serverFilePath);
+ return apiClient.RetrieveFileGetAsync(url, null, apiSession.ToHeadersCollection(), onReceiveProgress, cancellationToken);
+ }
+
+ public async Task> WebFileExistsAsync(ApiSession apiSession, string serverFilePath, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "files", serverFilePath);
+ var apiResult = await apiClient.HeadAsync(url, null, apiSession.ToHeadersCollection(), cancellationToken);
+ // http ok or http no content means that file exists
+ if (apiResult.IsSucceed)
+ {
+ return ApiResult.Ok(true);
+ }
+ else
+ {
+ // if not found, return Ok with false result
+ if(apiResult.Error is MorphApiNotFoundException)
+ {
+ return ApiResult.Ok(false);
+ }
+ else
+ {
+ // some error occured - return internal error from api result
+ return ApiResult.Fail(apiResult.Error);
+
+ }
+ }
+ }
+
+ public Task> WebFilesPutFileStreamAsync(ApiSession apiSession, string serverFolder, SendFileStreamData sendFileStreamData, Action onSendProgress, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ if (sendFileStreamData == null)
+ {
+ throw new ArgumentNullException(nameof(sendFileStreamData));
+ }
+
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "files", serverFolder);
+
+ return apiClient.PutFileStreamAsync(url,sendFileStreamData, null, apiSession.ToHeadersCollection(), onSendProgress, cancellationToken);
+
+ }
+
+ public Task> WebFilesPostFileStreamAsync(ApiSession apiSession, string serverFolder, SendFileStreamData sendFileStreamData, Action onSendProgress, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ if (sendFileStreamData == null)
+ {
+ throw new ArgumentNullException(nameof(sendFileStreamData));
+ }
+
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "files", serverFolder);
+
+ return apiClient.PostFileStreamAsync(url, sendFileStreamData, null, apiSession.ToHeadersCollection(), onSendProgress, cancellationToken);
+ }
+ public Task> WebFilesOpenContiniousPostStreamAsync(ApiSession apiSession, string serverFolder, string fileName, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "files", serverFolder);
+
+ return apiClient.PushContiniousStreamingDataAsync(HttpMethod.Post, url, new ContiniousStreamingRequest(fileName), null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+ public Task> WebFilesOpenContiniousPutStreamAsync(ApiSession apiSession, string serverFolder, string fileName, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+ var spaceName = apiSession.SpaceName;
+ var url = UrlHelper.JoinUrl("space", spaceName, "files", serverFolder);
+
+ return apiClient.PushContiniousStreamingDataAsync(HttpMethod.Put, url, new ContiniousStreamingRequest(fileName), null, apiSession.ToHeadersCollection(), cancellationToken);
+ }
+
+
+ }
+}
+
+
+
diff --git a/src/Client/MorphServerApiClient.cs b/src/Client/MorphServerApiClient.cs
index 6018b5a..eda43d2 100644
--- a/src/Client/MorphServerApiClient.cs
+++ b/src/Client/MorphServerApiClient.cs
@@ -1,72 +1,118 @@
-using Morph.Server.Sdk.Dto;
-using Morph.Server.Sdk.Exceptions;
-using Morph.Server.Sdk.Helper;
-using Morph.Server.Sdk.Model;
+using Morph.Server.Sdk.Model;
using System;
-using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
-using System.Runtime.Serialization.Json;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Net;
using Morph.Server.Sdk.Events;
-using System.Collections.Specialized;
using Morph.Server.Sdk.Dto.Commands;
-using Morph.Server.Sdk.Model.Errors;
using Morph.Server.Sdk.Mappers;
using Morph.Server.Sdk.Model.Commands;
using System.Linq;
-using Morph.Server.Sdk.Dto.Errors;
using System.Collections.Generic;
+using System.Reflection;
+using System.IO;
+using Morph.Server.Sdk.Model.InternalModels;
+using Morph.Server.Sdk.Dto;
+using System.Collections.Concurrent;
namespace Morph.Server.Sdk.Client
{
+
+
///
/// Morph Server api client V1
///
- public class MorphServerApiClient : IMorphServerApiClient, IDisposable
+ public class MorphServerApiClient : IMorphServerApiClient, IDisposable, ICanCloseSession
{
- protected readonly Uri _apiHost;
- protected readonly string UserAgent = "MorphServerApiClient/1.3.5";
- protected HttpClient _httpClient;
+
+ public event EventHandler OnDataDownloadProgress;
+ public event EventHandler OnDataUploadProgress;
+
+ protected readonly string _userAgent = "MorphServerApiClient/next";
protected readonly string _api_v1 = "api/v1/";
+ private readonly ILowLevelApiClient _lowLevelApiClient;
+ protected readonly IRestClient RestClient;
+ private ClientConfiguration clientConfiguration = new ClientConfiguration();
+
+ private bool _disposed = false;
+ private object _lock = new object();
+
+
+ public IClientConfiguration Config => clientConfiguration;
+
+ internal ILowLevelApiClient BuildApiClient(ClientConfiguration configuration)
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+#if NETSTANDARD2_0
+ // handler will be disposed automatically
+ HttpClientHandler aHandler = new HttpClientHandler()
+ {
+ ClientCertificateOptions = ClientCertificateOption.Automatic,
+ ServerCertificateCustomValidationCallback = configuration.ServerCertificateCustomValidationCallback
+ };
+#elif NET45
+ // handler will be disposed automatically
+ HttpClientHandler aHandler = new HttpClientHandler()
+ {
+ ClientCertificateOptions = ClientCertificateOption.Automatic
+ };
+#else
+ Not implemented
+#endif
+ var httpClient = BuildHttpClient(configuration, aHandler);
+ var restClient = ConstructRestApiClient(httpClient);
+ return new LowLevelApiClient(restClient);
+ }
+
///
/// Construct Api client
///
/// Server url
- public MorphServerApiClient(string apiHost)
+ public MorphServerApiClient(Uri apiHost)
{
- if (!apiHost.EndsWith("/"))
- apiHost += "/";
- _apiHost = new Uri(apiHost);
+ if (apiHost == null)
+ {
+ throw new ArgumentNullException(nameof(apiHost));
+ }
+ var defaultConfig = new ClientConfiguration
+ {
+ ApiUri = apiHost
+ };
+ clientConfiguration = defaultConfig;
+ _lowLevelApiClient = BuildApiClient(clientConfiguration);
+ RestClient = _lowLevelApiClient.RestClient;
}
- protected HttpClient GetHttpClient()
+ public MorphServerApiClient(ClientConfiguration clientConfiguration)
{
- if (_httpClient == null)
- {
- // handler will be disposed automatically
- HttpClientHandler aHandler = new HttpClientHandler()
- {
- ClientCertificateOptions = ClientCertificateOption.Automatic
- };
+ this.clientConfiguration = clientConfiguration ?? throw new ArgumentNullException(nameof(clientConfiguration));
+ _lowLevelApiClient = BuildApiClient(clientConfiguration);
+ RestClient = _lowLevelApiClient.RestClient;
+ }
+
- _httpClient = ConstructHttpClient(_apiHost, aHandler);
+ protected virtual IRestClient ConstructRestApiClient(HttpClient httpClient)
+ {
+ if (httpClient == null)
+ {
+ throw new ArgumentNullException(nameof(httpClient));
}
- return _httpClient;
- }
+ return new MorphServerRestClient(httpClient);
+ }
- public event EventHandler FileProgress;
- protected HttpClient ConstructHttpClient(Uri apiHost, HttpClientHandler httpClientHandler)
+ protected HttpClient BuildHttpClient(ClientConfiguration config, HttpClientHandler httpClientHandler)
{
if (httpClientHandler == null)
{
@@ -74,7 +120,7 @@ protected HttpClient ConstructHttpClient(Uri apiHost, HttpClientHandler httpClie
}
var client = new HttpClient(httpClientHandler, true);
- client.BaseAddress = new Uri(apiHost, new Uri(_api_v1, UriKind.Relative));
+ client.BaseAddress = new Uri(config.ApiUri, new Uri(_api_v1, UriKind.Relative));
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
@@ -82,8 +128,13 @@ protected HttpClient ConstructHttpClient(Uri apiHost, HttpClientHandler httpClie
{
CharSet = "utf-8"
});
- client.DefaultRequestHeaders.Add("User-Agent", UserAgent);
- client.DefaultRequestHeaders.Add("X-Client-Type", "EMS-CMD");
+ client.DefaultRequestHeaders.Add("User-Agent", _userAgent);
+ client.DefaultRequestHeaders.Add("X-Client-Type", config.ClientType);
+ client.DefaultRequestHeaders.Add("X-Client-Id", config.ClientId);
+ client.DefaultRequestHeaders.Add("X-Client-Sdk", config.SDKVersionString);
+
+ client.DefaultRequestHeaders.Add("Connection", "Keep-Alive");
+ client.DefaultRequestHeaders.Add("Keep-Alive", "timeout=120");
client.MaxResponseContentBufferSize = 100 * 1024;
client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue
@@ -94,497 +145,329 @@ protected HttpClient ConstructHttpClient(Uri apiHost, HttpClientHandler httpClie
- client.Timeout = TimeSpan.FromMinutes(15);
+ client.Timeout = config.HttpClientTimeout;
return client;
}
+
-
-
-
- protected static async Task HandleResponse(HttpResponseMessage response)
+ ///
+ /// Start Task like "fire and forget"
+ ///
+ public Task StartTaskAsync(ApiSession apiSession, StartTaskRequest startTaskRequest, CancellationToken cancellationToken)
{
- if (response.IsSuccessStatusCode)
+ if (apiSession == null)
{
- var content = await response.Content.ReadAsStringAsync();
- var result = JsonSerializationHelper.Deserialize(content);
- return result;
+ throw new ArgumentNullException(nameof(apiSession));
}
- else
- {
- await HandleErrorResponse(response);
- return default(T);
+ if (startTaskRequest == null)
+ {
+ throw new ArgumentNullException(nameof(startTaskRequest));
}
+ return Wrapped(async (token) =>
+ {
+ var requestDto = new TaskStartRequestDto();
+ if (startTaskRequest.TaskParameters != null)
+ {
+ requestDto.TaskParameters = startTaskRequest.TaskParameters.Select(TaskParameterMapper.ToDto).ToList();
+ }
+
+ if (!startTaskRequest.TaskId.HasValue)
+ {
+ throw new Exception("TaskId must be set.");
+ }
+ var apiResult = await _lowLevelApiClient.StartTaskAsync(apiSession, startTaskRequest.TaskId.Value, requestDto, token);
+ return MapOrFail(apiResult, (dto) => RunningTaskStatusMapper.RunningTaskStatusFromDto(dto));
+
+ }, cancellationToken, OperationType.ShortOperation);
}
- protected async Task HandleResponse(HttpResponseMessage response)
+ protected virtual async Task Wrapped(Func> fun, CancellationToken orginalCancellationToken, OperationType operationType)
{
- if (!response.IsSuccessStatusCode)
+ if (_disposed)
{
- await HandleErrorResponse(response);
+ throw new ObjectDisposedException(nameof(MorphServerApiClient));
}
- }
-
- private static async Task HandleErrorResponse(HttpResponseMessage response)
- {
+
+ TimeSpan maxExecutionTime;
+ switch (operationType)
+ {
+ case OperationType.FileTransfer:
+ maxExecutionTime = clientConfiguration.FileTransferTimeout; break;
+ case OperationType.ShortOperation:
+ maxExecutionTime = clientConfiguration.OperationTimeout; break;
+ case OperationType.SessionOpenAndRelated:
+ maxExecutionTime = clientConfiguration.SessionOpenTimeout; break;
+ default: throw new NotImplementedException();
+ }
- var content = await response.Content.ReadAsStringAsync();
- if (!string.IsNullOrWhiteSpace(content))
+
+ CancellationTokenSource derTokenSource =null;
+ try
{
- ErrorResponse errorResponse = null;
- try
- {
- errorResponse = JsonSerializationHelper.Deserialize(content);
- }
- catch (Exception)
+ derTokenSource = CancellationTokenSource.CreateLinkedTokenSource(orginalCancellationToken);
{
- throw new ResponseParseException("An error occurred while deserializing the response", content);
- }
- if (errorResponse.error == null)
- throw new ResponseParseException("An error occurred while deserializing the response", content);
+ derTokenSource.CancelAfter(maxExecutionTime);
+ try
+ {
+ return await fun(derTokenSource.Token);
+ }
- switch (errorResponse.error.code)
- {
- case ReadableErrorTopCode.Conflict: throw new MorphApiConflictException(errorResponse.error.message);
- case ReadableErrorTopCode.NotFound: throw new MorphApiNotFoundException(errorResponse.error.message);
- case ReadableErrorTopCode.Forbidden: throw new MorphApiForbiddenException(errorResponse.error.message);
- case ReadableErrorTopCode.Unauthorized: throw new MorphApiUnauthorizedException(errorResponse.error.message);
- case ReadableErrorTopCode.BadArgument: throw new MorphApiBadArgumentException(FieldErrorsMapper.MapFromDto(errorResponse.error), errorResponse.error.message);
- default: throw new MorphClientGeneralException(errorResponse.error.code, errorResponse.error.message);
+ catch (OperationCanceledException) when (!orginalCancellationToken.IsCancellationRequested && derTokenSource.IsCancellationRequested)
+ {
+ if (operationType == OperationType.SessionOpenAndRelated)
+ {
+ throw new Exception($"Can't connect to host {clientConfiguration.ApiUri}. Operation timeout ({maxExecutionTime})");
+ }
+ else
+ {
+ throw new Exception($"Operation timeout ({maxExecutionTime}) when processing command to host {clientConfiguration.ApiUri}");
+ }
+ }
}
}
-
- else
+ finally
{
- switch (response.StatusCode)
+ if (derTokenSource != null)
{
- case HttpStatusCode.Conflict: throw new MorphApiConflictException(response.ReasonPhrase ?? "Conflict");
- case HttpStatusCode.NotFound: throw new MorphApiNotFoundException(response.ReasonPhrase ?? "Not found");
- case HttpStatusCode.Forbidden: throw new MorphApiForbiddenException(response.ReasonPhrase ?? "Forbidden");
- case HttpStatusCode.Unauthorized: throw new MorphApiUnauthorizedException(response.ReasonPhrase ?? "Unauthorized");
- case HttpStatusCode.BadRequest: throw new MorphClientGeneralException("Unknown", response.ReasonPhrase ?? "Unknown error");
- default: throw new ResponseParseException(response.ReasonPhrase, null);
+ if (operationType == OperationType.FileTransfer)
+ {
+ RegisterForDisposing(derTokenSource);
+ }
+ else
+ {
+ derTokenSource.Dispose();
+ }
}
+ }
+
+ }
+
+ private ConcurrentBag _ctsForDisposing = new ConcurrentBag();
+ private void RegisterForDisposing(CancellationTokenSource derTokenSource)
+ {
+ if (derTokenSource == null)
+ {
+ throw new ArgumentNullException(nameof(derTokenSource));
}
+
+ _ctsForDisposing.Add(derTokenSource);
}
- ///
- /// Start Task like "fire and forget"
- ///
- /// api session
- /// tast guid
- ///
- ///
- ///
- public async Task StartTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken, IEnumerable taskParameters = null)
+ protected virtual void FailIfError(ApiResult apiResult)
{
- if (apiSession == null)
+ if (!apiResult.IsSucceed)
{
- throw new ArgumentNullException(nameof(apiSession));
+ throw apiResult.Error;
}
+ }
+
+
- var spaceName = apiSession.SpaceName;
- var url = UrlHelper.JoinUrl("space", spaceName, "runningtasks", taskId.ToString("D"), "payload");
- var dto = new TaskStartRequestDto();
- if (taskParameters != null)
+ protected virtual TDataModel MapOrFail(ApiResult apiResult, Func maper)
+ {
+ if (apiResult.IsSucceed)
{
- dto.TaskParameters = taskParameters.Select(TaskParameterMapper.ToDto).ToList();
+ return maper(apiResult.Data);
}
- var request = JsonSerializationHelper.SerializeAsStringContent(dto);
- using (var response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Post, url, request, apiSession), cancellationToken))
+ else
{
- var info = await HandleResponse(response);
- return RunningTaskStatusMapper.RunningTaskStatusFromDto(info);
+ throw apiResult.Error;
}
-
}
+
///
- /// Gets status of the task (Running/Not running) and payload
+ /// Close opened session
///
/// api session
- /// task guid
- /// cancellation token
- /// Returns task status
- private async Task GetRunningTaskStatusAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
+ ///
+ ///
+ Task ICanCloseSession.CloseSessionAsync(ApiSession apiSession, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
+ if (apiSession.IsClosed)
+ return Task.FromResult(0);
+ if (apiSession.IsAnonymous)
+ return Task.FromResult(0);
- var spaceName = apiSession.SpaceName;
- var nvc = new NameValueCollection();
- nvc.Add("_", DateTime.Now.Ticks.ToString());
- var url = UrlHelper.JoinUrl("space", spaceName, "runningtasks", taskId.ToString("D")) + nvc.ToQueryString();
-
- using (var response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Get, url, null, apiSession), cancellationToken))
+ return Wrapped(async (token) =>
{
- var info = await HandleResponse(response);
- return RunningTaskStatusMapper.RunningTaskStatusFromDto(info);
- }
+ var apiResult = await _lowLevelApiClient.AuthLogoutAsync(apiSession, token);
+ // if task fail - do nothing. server will close this session after inactivity period
+ return Task.FromResult(0);
+
+ }, cancellationToken, OperationType.ShortOperation);
+
}
///
- /// Gets status of the task
+ /// Gets status of the task (Running/Not running) and payload
///
/// api session
/// task guid
/// cancellation token
/// Returns task status
- public async Task GetTaskStatusAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
+ private Task GetRunningTaskStatusAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
- var spaceName = apiSession.SpaceName;
- var nvc = new NameValueCollection();
- nvc.Add("_", DateTime.Now.Ticks.ToString());
- var url = UrlHelper.JoinUrl("space", spaceName, "tasks", taskId.ToString("D")) + nvc.ToQueryString();
- using (var response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Get, url, null, apiSession), cancellationToken))
+ return Wrapped(async (token) =>
{
- var dto = await HandleResponse(response);
- var data = TaskStatusMapper.MapFromDto(dto);
- return data;
- }
+ var apiResult = await _lowLevelApiClient.GetRunningTaskStatusAsync(apiSession, taskId, token);
+ return MapOrFail(apiResult, (dto) => RunningTaskStatusMapper.RunningTaskStatusFromDto(dto));
+
+ }, cancellationToken, OperationType.ShortOperation);
+
}
+
///
- /// Stops the Task
+ /// Gets status of the task
///
/// api session
- ///
+ /// task guid
/// cancellation token
- ///
- public async Task StopTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
+ /// Returns task status
+ public Task GetTaskStatusAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
-
- var spaceName = apiSession.SpaceName;
- var url = UrlHelper.JoinUrl("space", spaceName, "runningtasks", taskId.ToString("D"));
- using (var response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Delete, url, null, apiSession), cancellationToken))
- {
- await HandleResponse(response);
- }
- }
-
- ///
- /// Returns server status. May raise exception if server is unreachable
- ///
- ///
- public async Task GetServerStatusAsync(CancellationToken cancellationToken)
- {
- return await GetDataWithCancelAfter(async (token) =>
+ return Wrapped(async (token) =>
{
- var nvc = new NameValueCollection();
- nvc.Add("_", DateTime.Now.Ticks.ToString());
+ var apiResult = await _lowLevelApiClient.GetTaskStatusAsync(apiSession, taskId, token);
+ return MapOrFail(apiResult, (dto) => TaskStatusMapper.MapFromDto(dto));
- var url = "server/status" + nvc.ToQueryString();
- using (var response = await GetHttpClient().GetAsync(url, token))
- {
- var dto = await HandleResponse(response);
- var result = ServerStatusMapper.MapFromDto(dto);
- return result;
+ }, cancellationToken, OperationType.ShortOperation);
- }
- }, TimeSpan.FromSeconds(20), cancellationToken);
}
///
- /// Download file from server
+ /// Change task mode
///
/// api session
- /// Path to the remote file. Like /some/folder/file.txt
- /// stream for writing. You should dispose the stream by yourself
+ /// task guid
+ ///
/// cancellation token
- /// returns file info
- public async Task DownloadFileAsync(ApiSession apiSession, string remoteFilePath, Stream streamToWriteTo, CancellationToken cancellationToken)
+ /// Returns task status
+ public Task TaskChangeModeAsync(ApiSession apiSession, Guid taskId, TaskChangeModeRequest taskChangeModeRequest, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
- DownloadFileInfo fileInfo = null;
- await DownloadFileAsync(apiSession, remoteFilePath, (fi) => { fileInfo = fi; return true; }, streamToWriteTo, cancellationToken);
- return fileInfo;
- }
- ///
- /// Download file from server
- ///
- /// api session
- /// Path to the remote file. Like /some/folder/file.txt
- /// delegate to check file info before accessing to the file stream
- /// stream for writing. Writing will be executed only if handleFile delegate returns true
- ///
- ///
- public async Task DownloadFileAsync(ApiSession apiSession, string remoteFilePath, Func handleFile, Stream streamToWriteTo, CancellationToken cancellationToken)
- {
- if (apiSession == null)
+ if (taskChangeModeRequest is null)
{
- throw new ArgumentNullException(nameof(apiSession));
+ throw new ArgumentNullException(nameof(taskChangeModeRequest));
}
- var spaceName = apiSession.SpaceName;
- var nvc = new NameValueCollection();
- nvc.Add("_", DateTime.Now.Ticks.ToString());
- var url = UrlHelper.JoinUrl("space", spaceName, "files", remoteFilePath) + nvc.ToQueryString();
- // it's necessary to add HttpCompletionOption.ResponseHeadersRead to disable caching
- using (HttpResponseMessage response = await GetHttpClient()
- .SendAsync(BuildHttpRequestMessage(HttpMethod.Get, url, null, apiSession), HttpCompletionOption.ResponseHeadersRead, cancellationToken))
- if (response.IsSuccessStatusCode)
- {
- using (Stream streamToReadFrom = await response.Content.ReadAsStreamAsync())
- {
- var contentDisposition = response.Content.Headers.ContentDisposition;
- DownloadFileInfo dfi = null;
- if (contentDisposition != null)
- {
- dfi = new DownloadFileInfo
- {
- // need to fix double quotes, that may come from server response
- // FileNameStar contains file name encoded in UTF8
- FileName = (contentDisposition.FileNameStar ?? contentDisposition.FileName).TrimStart('\"').TrimEnd('\"')
- };
- }
- var contentLength = response.Content.Headers.ContentLength;
- var fileProgress = new FileProgress(dfi.FileName, contentLength.Value);
- fileProgress.StateChanged += DownloadProgress_StateChanged;
-
- var bufferSize = 4096;
- if (handleFile(dfi))
- {
-
- var buffer = new byte[bufferSize];
- var size = contentLength.Value;
- var processed = 0;
- var lastUpdate = DateTime.MinValue;
-
- fileProgress.ChangeState(FileProgressState.Starting);
-
- while (true)
- {
- // cancel download if cancellation token triggered
- if (cancellationToken.IsCancellationRequested)
- {
- fileProgress.ChangeState(FileProgressState.Cancelled);
- throw new OperationCanceledException();
- }
-
- var length = await streamToReadFrom.ReadAsync(buffer, 0, buffer.Length);
- if (length <= 0) break;
- await streamToWriteTo.WriteAsync(buffer, 0, length);
- processed += length;
- if (DateTime.Now - lastUpdate > TimeSpan.FromMilliseconds(250))
- {
- fileProgress.SetProcessedBytes(processed);
- fileProgress.ChangeState(FileProgressState.Processing);
- lastUpdate = DateTime.Now;
- }
-
- }
-
- fileProgress.ChangeState(FileProgressState.Finishing);
-
- }
-
- }
- }
- else
+ return Wrapped(async (token) =>
+ {
+ var request = new SpaceTaskChangeModeRequestDto
{
- // TODO: check
- await HandleErrorResponse(response);
-
- }
+ TaskEnabled = taskChangeModeRequest.TaskEnabled
+ };
+ var apiResult = await _lowLevelApiClient.TaskChangeModeAsync(apiSession, taskId, request, token);
+ return MapOrFail(apiResult, (dto) => SpaceTaskMapper.MapFull(dto));
+ }, cancellationToken, OperationType.ShortOperation);
}
+
///
- /// Uploads file to the server
+ /// Retrieves space status
///
/// api session
- /// path to the local file
- /// detination folder like /path/to/folder
- /// cancellation token
- /// overwrite file
+ ///
///
- public async Task UploadFileAsync(ApiSession apiSession, string localFilePath, string destFolderPath, CancellationToken cancellationToken, bool overwriteFileifExists = false)
+ public Task GetSpaceStatusAsync(ApiSession apiSession, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
- if (!File.Exists(localFilePath))
- throw new FileNotFoundException(string.Format("File '{0}' not found", localFilePath));
- var fileSize = new System.IO.FileInfo(localFilePath).Length;
- var fileName = Path.GetFileName(localFilePath);
- using (var fsSource = new FileStream(localFilePath, FileMode.Open, FileAccess.Read))
+ return Wrapped(async (token) =>
{
- await UploadFileAsync(apiSession, fsSource, fileName, fileSize, destFolderPath, cancellationToken, overwriteFileifExists);
- return;
- }
+ var apiResult = await _lowLevelApiClient.SpacesGetSpaceStatusAsync(apiSession, apiSession.SpaceName, token);
+ return MapOrFail(apiResult, (dto) => SpaceStatusMapper.MapFromDto(dto));
+
+ }, cancellationToken, OperationType.ShortOperation);
}
+
///
- /// Uploads local file to the server folder.
+ /// Stops the Task
///
/// api session
- /// path to the local file
- /// destination folder like /path/to/folder
- /// destination filename. If it's empty then original file name will be used
+ ///
/// cancellation token
- /// overwrite file
///
- public async Task UploadFileAsync(ApiSession apiSession, string localFilePath, string destFolderPath, string destFileName, CancellationToken cancellationToken, bool overwriteFileifExists = false)
+ public async Task StopTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
- if (!File.Exists(localFilePath))
- {
- throw new FileNotFoundException(string.Format("File '{0}' not found", localFilePath));
- }
- var fileName = String.IsNullOrWhiteSpace(destFileName) ? Path.GetFileName(localFilePath) : destFileName;
- var fileSize = new FileInfo(localFilePath).Length;
- using (var fsSource = new FileStream(localFilePath, FileMode.Open, FileAccess.Read))
+ await Wrapped(async (token) =>
{
- await UploadFileAsync(apiSession, fsSource, fileName, fileSize, destFolderPath, cancellationToken, overwriteFileifExists);
- return;
- }
-
- }
+ var apiResult = await _lowLevelApiClient.StopTaskAsync(apiSession, taskId, token);
+ FailIfError(apiResult);
+ return Task.FromResult(0);
+ }, cancellationToken, OperationType.ShortOperation);
- protected HttpRequestMessage BuildHttpRequestMessage(HttpMethod httpMethod, string url, HttpContent content, ApiSession apiSession)
- {
- var requestMessage = new HttpRequestMessage()
- {
- Content = content,
- Method = httpMethod,
- RequestUri = new Uri(url, UriKind.Relative)
- };
- if (apiSession != null && !apiSession.IsAnonymous && !apiSession.IsClosed)
- {
- requestMessage.Headers.Add(ApiSession.AuthHeaderName, apiSession.AuthToken);
- }
- return requestMessage;
}
-
///
- /// Upload file stream to the server
+ /// Returns server status. May raise exception if server is unreachable
///
- /// api session
- /// stream for read from
- /// file name
- /// file size in bytes
- /// destination folder like /path/to/folder
- /// cancellation tokern
- ///
///
- public async Task UploadFileAsync(ApiSession apiSession, Stream inputStream, string fileName, long fileSize, string destFolderPath, CancellationToken cancellationToken, bool overwriteFileifExists = false)
+ public Task GetServerStatusAsync(CancellationToken cancellationToken)
{
- if (apiSession == null)
- {
- throw new ArgumentNullException(nameof(apiSession));
- }
-
- try
+ return Wrapped(async (token) =>
{
- var spaceName = apiSession.SpaceName;
- string boundary = "EasyMorphCommandClient--------" + Guid.NewGuid().ToString("N");
- string url = UrlHelper.JoinUrl("space", spaceName, "files", destFolderPath);
+ var apiResult = await _lowLevelApiClient.ServerGetStatusAsync(token);
+ return MapOrFail(apiResult, (dto) => ServerStatusMapper.MapFromDto(dto));
- using (var content = new MultipartFormDataContent(boundary))
- {
- var downloadProgress = new FileProgress(fileName, fileSize);
- downloadProgress.StateChanged += DownloadProgress_StateChanged;
- using (cancellationToken.Register(() => downloadProgress.ChangeState(FileProgressState.Cancelled)))
- {
- using (var streamContent = new ProgressStreamContent(inputStream, downloadProgress))
- {
- content.Add(streamContent, "files", Path.GetFileName(fileName));
-
- var requestMessage = BuildHttpRequestMessage(overwriteFileifExists ? HttpMethod.Put : HttpMethod.Post, url, content, apiSession);
- using (requestMessage)
- {
- using (var response = await GetHttpClient().SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
- {
- await HandleResponse(response);
- }
- }
- }
- }
- }
- }
- catch (Exception ex)
- when (ex.InnerException != null && ex.InnerException is WebException)
- {
- var einner = ex.InnerException as WebException;
- if (einner.Status == WebExceptionStatus.ConnectionClosed)
- throw new MorphApiNotFoundException("Specified folder not found");
-
- }
+ }, cancellationToken, OperationType.SessionOpenAndRelated);
}
- private void DownloadProgress_StateChanged(object sender, FileEventArgs e)
+ public async Task GetSpacesListAsync(CancellationToken cancellationToken)
{
- if (FileProgress != null)
+ return await Wrapped(async (token) =>
{
- FileProgress(this, e);
- }
+ var apiResult = await _lowLevelApiClient.SpacesGetListAsync(token);
+ return MapOrFail(apiResult, (dto) => SpacesEnumerationMapper.MapFromDto(dto));
+ }, cancellationToken, OperationType.SessionOpenAndRelated);
}
-
- protected async Task GetDataWithCancelAfter(Func> action, TimeSpan timeout, CancellationToken cancellationToken)
+ private void DownloadProgress_StateChanged(object sender, FileTransferProgressEventArgs e)
{
- using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
- {
- linkedTokenSource.CancelAfter(timeout);
- try
- {
- return await action(linkedTokenSource.Token);
- }
-
- catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested && linkedTokenSource.IsCancellationRequested)
- {
- throw new Exception($"Can't connect to host {_apiHost}. Operation timeout ({timeout})");
- }
- }
+ OnDataDownloadProgress?.Invoke(this, e);
}
- public async Task GetSpacesListAsync(CancellationToken cancellationToken)
- {
- return await GetDataWithCancelAfter(async (token) =>
- {
- var nvc = new NameValueCollection();
- nvc.Add("_", DateTime.Now.Ticks.ToString());
- var url = "spaces/list" + nvc.ToQueryString();
- using (var response = await GetHttpClient().GetAsync(url, token))
- {
- var dto = await HandleResponse(response);
- return SpacesEnumerationMapper.MapFromDto(dto);
- }
- }, TimeSpan.FromSeconds(20), cancellationToken);
- }
+
///
@@ -594,26 +477,22 @@ public async Task GetSpacesListAsync(CancellationToken ca
/// folder path like /path/to/folder
///
///
- public async Task BrowseSpaceAsync(ApiSession apiSession, string folderPath, CancellationToken cancellationToken)
+ public Task SpaceBrowseAsync(ApiSession apiSession, string folderPath, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
- var spaceName = apiSession.SpaceName;
- var nvc = new NameValueCollection();
- nvc.Add("_", DateTime.Now.Ticks.ToString());
-
- var url = UrlHelper.JoinUrl("space", spaceName, "browse", folderPath) + nvc.ToQueryString();
- using (var response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Get, url, null, apiSession), cancellationToken))
+ return Wrapped(async (token) =>
{
- var dto = await HandleResponse(response);
- return SpaceBrowsingMapper.MapFromDto(dto);
+ var apiResult = await _lowLevelApiClient.WebFilesBrowseSpaceAsync(apiSession, folderPath, token);
+ return MapOrFail(apiResult, (dto) => SpaceBrowsingMapper.MapFromDto(dto));
- }
+ }, cancellationToken, OperationType.ShortOperation);
}
+
///
/// Checks if file exists
///
@@ -622,24 +501,26 @@ public async Task BrowseSpaceAsync(ApiSession apiSession, str
/// file name
///
/// Returns true if file exists.
- public async Task FileExistsAsync(ApiSession apiSession, string serverFolder, string fileName, CancellationToken cancellationToken)
+ public Task SpaceFileExistsAsync(ApiSession apiSession, string serverFilePath, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
- if (string.IsNullOrWhiteSpace(fileName))
- throw new ArgumentException(nameof(fileName));
- var browseResult = await this.BrowseSpaceAsync(apiSession, serverFolder, cancellationToken);
+ if (string.IsNullOrWhiteSpace(serverFilePath))
+ {
+ throw new ArgumentException(nameof(serverFilePath));
+ }
- return browseResult.FileExists(fileName);
+ return Wrapped(async (token) =>
+ {
+ var apiResult = await _lowLevelApiClient.WebFileExistsAsync(apiSession, serverFilePath, token);
+ return MapOrFail(apiResult, (dto) => dto);
+ }, cancellationToken, OperationType.ShortOperation);
}
-
-
-
///
/// Performs file deletion
///
@@ -648,48 +529,26 @@ public async Task FileExistsAsync(ApiSession apiSession, string serverFold
/// file name
///
///
- public async Task DeleteFileAsync(ApiSession apiSession, string serverFolder, string fileName, CancellationToken cancellationToken)
+ public Task SpaceDeleteFileAsync(ApiSession apiSession, string serverFilePath, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
- var spaceName = apiSession.SpaceName;
- var url = UrlHelper.JoinUrl("space", spaceName, "files", serverFolder, fileName);
-
- using (HttpResponseMessage response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Delete, url, null, apiSession), cancellationToken))
+ return Wrapped(async (token) =>
{
- await HandleResponse(response);
- }
+ var apiResult = await _lowLevelApiClient.WebFilesDeleteFileAsync(apiSession, serverFilePath, token);
+ FailIfError(apiResult);
+ return Task.FromResult(0);
+
+ }, cancellationToken, OperationType.ShortOperation);
}
- ///
- /// Retrieves space status
- ///
- /// api session
- ///
- ///
- public async Task GetSpaceStatusAsync(ApiSession apiSession, CancellationToken cancellationToken)
- {
- if (apiSession == null)
- {
- throw new ArgumentNullException(nameof(apiSession));
- }
-
- var spaceName = apiSession.SpaceName;
- var url = UrlHelper.JoinUrl("spaces", spaceName, "status");
- using (HttpResponseMessage response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Get, url, null, apiSession), cancellationToken))
- {
- var dto = await HandleResponse(response);
- var entity = SpaceStatusMapper.MapFromDto(dto);
- return entity;
- }
- }
///
@@ -699,7 +558,7 @@ public async Task GetSpaceStatusAsync(ApiSession apiSession, Cancel
/// project path like /path/to/project.morph
///
///
- public async Task ValidateTasksAsync(ApiSession apiSession, string projectPath, CancellationToken cancellationToken)
+ public Task ValidateTasksAsync(ApiSession apiSession, string projectPath, CancellationToken cancellationToken)
{
if (apiSession == null)
{
@@ -707,102 +566,25 @@ public async Task ValidateTasksAsync(ApiSession apiSession,
}
if (string.IsNullOrWhiteSpace(projectPath))
- throw new ArgumentException(nameof(projectPath));
- var spaceName = apiSession.SpaceName;
- var url = "commands/validatetasks";
- var request = new ValidateTasksRequestDto
{
- SpaceName = spaceName,
- ProjectPath = projectPath
- };
- using (var response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Post, url, JsonSerializationHelper.SerializeAsStringContent(request), apiSession), cancellationToken))
- {
-
- var dto = await HandleResponse(response);
- var entity = ValidateTasksResponseMapper.MapFromDto(dto);
- return entity;
-
+ throw new ArgumentException("projectPath is empty", nameof(projectPath));
}
- }
-
-
-
- protected static async Task internalGetAuthNonceAsync(HttpClient httpClient, CancellationToken cancellationToken)
- {
- var url = "auth/nonce";
- using (var response = await httpClient.PostAsync(url, JsonSerializationHelper.SerializeAsStringContent(new GenerateNonceRequestDto()), cancellationToken))
+ return Wrapped(async (token) =>
{
- var dto = await HandleResponse(response);
- return dto.Nonce;
-
- }
- }
-
- protected async Task internalAuthLoginAsync(string clientNonce, string serverNonce, string spaceName, string passwordHash, CancellationToken cancellationToken)
- {
- var url = "auth/login";
- var requestDto = new LoginRequestDto
- {
- ClientSeed = clientNonce,
- Password = passwordHash,
- Provider = "Space",
- UserName = spaceName,
- RequestToken = serverNonce
- };
+ var request = new ValidateTasksRequestDto
+ {
+ SpaceName = apiSession.SpaceName,
+ ProjectPath = projectPath
+ };
+ var apiResult = await _lowLevelApiClient.ValidateTasksAsync(apiSession, request, token);
+ return MapOrFail(apiResult, (dto) => ValidateTasksResponseMapper.MapFromDto(dto));
- using (var response = await GetHttpClient().PostAsync(url, JsonSerializationHelper.SerializeAsStringContent(requestDto), cancellationToken))
- {
- var responseDto = await HandleResponse(response);
- return responseDto.Token;
- }
- }
- protected static async Task internalAuthExternalWindowAsync(HttpClient httpClient, string spaceName, string serverNonce, CancellationToken cancellationToken)
- {
- var url = "auth/external/windows";
- var requestDto = new WindowsExternalLoginRequestDto
- {
- RequestToken = serverNonce,
- SpaceName = spaceName
- };
+ }, cancellationToken, OperationType.ShortOperation);
- using (var response = await httpClient.PostAsync(url, JsonSerializationHelper.SerializeAsStringContent(requestDto), cancellationToken))
- {
- var responseDto = await HandleResponse(response);
- return responseDto.Token;
- }
}
- protected async Task OpenSessionViaWindowsAuthenticationAsync(string spaceName, CancellationToken cancellationToken)
- {
- if (string.IsNullOrEmpty(spaceName))
- {
- throw new ArgumentException("Space name is not set", nameof(spaceName));
- }
- // handler will be disposed automatically
- HttpClientHandler aHandler = new HttpClientHandler()
- {
- ClientCertificateOptions = ClientCertificateOption.Automatic,
- UseDefaultCredentials = true
- };
-
- using (var httpClient = ConstructHttpClient(_apiHost, aHandler))
- {
-
- var serverNonce = await internalGetAuthNonceAsync(httpClient, cancellationToken);
- var token = await internalAuthExternalWindowAsync(httpClient, spaceName, serverNonce, cancellationToken);
-
- return new ApiSession(this)
- {
- AuthToken = token,
- IsAnonymous = false,
- IsClosed = false,
- SpaceName = spaceName
- };
- }
- }
-
///
/// Opens session based on required authentication mechanism
@@ -823,161 +605,174 @@ public async Task OpenSessionAsync(OpenSessionRequest openSessionReq
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
- // no more than 20 sec for session opening
- var timeout = TimeSpan.FromSeconds(20);
+
+ var timeout = clientConfiguration.SessionOpenTimeout;
linkedTokenSource.CancelAfter(timeout);
var cancellationToken = linkedTokenSource.Token;
try
{
- var spacesListResult = await GetSpacesListAsync(cancellationToken);
+ var spacesListApiResult = await _lowLevelApiClient.SpacesGetListAsync(cancellationToken);
+ var spacesListResult = MapOrFail(spacesListApiResult, (dto) => SpacesEnumerationMapper.MapFromDto(dto));
+
var desiredSpace = spacesListResult.Items.FirstOrDefault(x => x.SpaceName.Equals(openSessionRequest.SpaceName, StringComparison.OrdinalIgnoreCase));
if (desiredSpace == null)
{
throw new Exception($"Server has no space '{openSessionRequest.SpaceName}'");
}
- // space access restriction is supported since server 3.9.2
- // for previous versions api will return SpaceAccessRestriction.NotSupported
- // a special fall-back mechanize need to be used to open session in such case
- switch (desiredSpace.SpaceAccessRestriction)
- {
- // anon space
- case SpaceAccessRestriction.None:
- return ApiSession.Anonymous(openSessionRequest.SpaceName);
-
- // password protected space
- case SpaceAccessRestriction.BasicPassword:
- return await OpenSessionViaSpacePasswordAsync(openSessionRequest.SpaceName, openSessionRequest.Password, cancellationToken);
-
- // windows authentication
- case SpaceAccessRestriction.WindowsAuthentication:
- return await OpenSessionViaWindowsAuthenticationAsync(openSessionRequest.SpaceName, cancellationToken);
-
- // fallback
- case SpaceAccessRestriction.NotSupported:
-
- // if space is public or password is not set - open anon session
- if (desiredSpace.IsPublic || string.IsNullOrWhiteSpace(openSessionRequest.Password))
- {
- return ApiSession.Anonymous(openSessionRequest.SpaceName);
- }
- // otherwise open session via space password
- else
- {
- return await OpenSessionViaSpacePasswordAsync(openSessionRequest.SpaceName, openSessionRequest.Password, cancellationToken);
- }
-
- default:
- throw new Exception("Space access restriction method is not supported by this client.");
- }
+ var session = await MorphServerAuthenticator.OpenSessionMultiplexedAsync(desiredSpace,
+ new OpenSessionAuthenticatorContext(
+ _lowLevelApiClient,
+ this as ICanCloseSession,
+ (handler) => ConstructRestApiClient(BuildHttpClient(clientConfiguration, handler))),
+ openSessionRequest, cancellationToken);
+
+ return session;
}
catch (OperationCanceledException) when (!ct.IsCancellationRequested && linkedTokenSource.IsCancellationRequested)
{
- throw new Exception($"Can't connect to host {_apiHost}. Operation timeout ({timeout})");
+ throw new Exception($"Can't connect to host {clientConfiguration.ApiUri}. Operation timeout ({timeout})");
}
}
}
- ///
- /// Open a new authenticated session via password
- ///
- /// space name
- /// space password
- ///
- ///
- public async Task OpenSessionViaSpacePasswordAsync(string spaceName, string password, CancellationToken cancellationToken)
- {
- if (string.IsNullOrEmpty(spaceName))
- {
- throw new ArgumentException("Space name is not set.", nameof(spaceName));
- }
- if (password == null)
- {
- throw new ArgumentNullException(nameof(password));
- }
- var passwordHash = CryptographyHelper.CalculateSha256HEX(password);
- var serverNonce = await internalGetAuthNonceAsync(GetHttpClient(), cancellationToken);
- var clientNonce = ConvertHelper.ByteArrayToHexString(CryptographyHelper.GenerateRandomSequence(16));
- var all = passwordHash + serverNonce + clientNonce;
- var allHash = CryptographyHelper.CalculateSha256HEX(all);
+ public Task GetTasksListAsync(ApiSession apiSession, CancellationToken cancellationToken)
+ {
+ return Wrapped(async (token) =>
+ {
+ var apiResult = await _lowLevelApiClient.GetTasksListAsync(apiSession, token);
+ return MapOrFail(apiResult, (dto) => SpaceTasksListsMapper.MapFromDto(dto));
- var token = await internalAuthLoginAsync(clientNonce, serverNonce, spaceName, allHash, cancellationToken);
+ }, cancellationToken, OperationType.ShortOperation);
- return new ApiSession(this)
+ }
+
+ public Task GetTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
+ {
+ return Wrapped(async (token) =>
{
- AuthToken = token,
- IsAnonymous = false,
- IsClosed = false,
- SpaceName = spaceName
- };
+ var apiResult = await _lowLevelApiClient.GetTaskAsync(apiSession, taskId, token);
+ return MapOrFail(apiResult, (dto) => SpaceTaskMapper.MapFull(dto));
+
+ }, cancellationToken, OperationType.ShortOperation);
}
- ///
- /// Close opened session
- ///
- /// api session
- ///
- ///
- public async Task CloseSessionAsync(ApiSession apiSession, CancellationToken cancellationToken)
+
+ public Task SpaceOpenStreamingDataAsync(ApiSession apiSession, string remoteFilePath, CancellationToken cancellationToken)
{
if (apiSession == null)
{
throw new ArgumentNullException(nameof(apiSession));
}
- if (apiSession.IsClosed)
- return;
- if (apiSession.IsAnonymous)
- return;
+ return Wrapped(async (token) =>
+ {
+ Action onReceiveProgress = (data) =>
+ {
+ OnDataDownloadProgress?.Invoke(this, data);
+ };
+ var apiResult = await _lowLevelApiClient.WebFilesDownloadFileAsync(apiSession, remoteFilePath, onReceiveProgress, token);
+ return MapOrFail(apiResult, (data) => new ServerStreamingData(data.Stream, data.FileName, data.FileSize)
+ );
- var url = "auth/logout";
+ }, cancellationToken, OperationType.FileTransfer);
+ }
- using (var response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Post, url, null, apiSession), cancellationToken))
+ public void Dispose()
+ {
+ lock (_lock)
{
+ if (_disposed)
+ return;
+ if (_lowLevelApiClient != null)
+ _lowLevelApiClient.Dispose();
+
+ Array.ForEach(_ctsForDisposing.ToArray(), z => z.Dispose());
+ _disposed = true;
+ }
+ }
- await HandleResponse(response);
-
+ public Task SpaceOpenDataStreamAsync(ApiSession apiSession, string remoteFilePath, CancellationToken cancellationToken)
+ {
+ if (apiSession == null)
+ {
+ throw new ArgumentNullException(nameof(apiSession));
}
+ return Wrapped(async (token) =>
+ {
+ Action onReceiveProgress = (data) =>
+ {
+ OnDataDownloadProgress?.Invoke(this, data);
+ };
+ var apiResult = await _lowLevelApiClient.WebFilesDownloadFileAsync(apiSession, remoteFilePath, onReceiveProgress, token);
+ return MapOrFail(apiResult, (data) => data.Stream);
+ }, cancellationToken, OperationType.FileTransfer);
}
- public async Task GetTasksListAsync(ApiSession apiSession, CancellationToken cancellationToken)
+ public Task SpaceUploadContiniousStreamingAsync(ApiSession apiSession, SpaceUploadContiniousStreamRequest continiousStreamRequest, CancellationToken cancellationToken)
{
- var nvc = new NameValueCollection();
- nvc.Add("_", DateTime.Now.Ticks.ToString());
- var url = UrlHelper.JoinUrl("space", apiSession.SpaceName, "tasks");
- using (var response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Get, url, null, apiSession), cancellationToken))
+ if (apiSession == null)
{
- var dto = await HandleResponse(response);
- return SpaceTasksListsMapper.MapFromDto(dto);
+ throw new ArgumentNullException(nameof(apiSession));
}
- }
- public async Task GetTaskAsync(ApiSession apiSession, Guid taskId, CancellationToken cancellationToken)
- {
- var nvc = new NameValueCollection();
- nvc.Add("_", DateTime.Now.Ticks.ToString());
- var url = UrlHelper.JoinUrl("space", apiSession.SpaceName, "tasks", taskId.ToString("D"));
- using (var response = await GetHttpClient().SendAsync(BuildHttpRequestMessage(HttpMethod.Get, url, null, apiSession), cancellationToken))
+ if (continiousStreamRequest == null)
{
- var dto = await HandleResponse(response);
- return SpaceTaskMapper.MapFull(dto);
+ throw new ArgumentNullException(nameof(continiousStreamRequest));
}
+
+ return Wrapped(async (token) =>
+ {
+ var apiResult =
+ continiousStreamRequest.OverwriteExistingFile ?
+ await _lowLevelApiClient.WebFilesOpenContiniousPutStreamAsync(apiSession, continiousStreamRequest.ServerFolder, continiousStreamRequest.FileName, token) :
+ await _lowLevelApiClient.WebFilesOpenContiniousPostStreamAsync(apiSession, continiousStreamRequest.ServerFolder, continiousStreamRequest.FileName, token);
+
+ var connection = MapOrFail(apiResult, c => c);
+ return new ContiniousStreamingConnection(connection);
+
+ }, cancellationToken, OperationType.FileTransfer);
+
}
- public void Dispose()
+ public Task SpaceUploadDataStreamAsync(ApiSession apiSession, SpaceUploadDataStreamRequest spaceUploadFileRequest, CancellationToken cancellationToken)
{
- if (_httpClient != null)
+ if (apiSession == null)
{
- _httpClient.Dispose();
- _httpClient = null;
+ throw new ArgumentNullException(nameof(apiSession));
+ }
+
+ if (spaceUploadFileRequest == null)
+ {
+ throw new ArgumentNullException(nameof(spaceUploadFileRequest));
}
+
+ return Wrapped(async (token) =>
+ {
+ Action onSendProgress = (data) =>
+ {
+ OnDataUploadProgress?.Invoke(this, data);
+ };
+ var sendStreamData = new SendFileStreamData(
+ spaceUploadFileRequest.DataStream,
+ spaceUploadFileRequest.FileName,
+ spaceUploadFileRequest.FileSize);
+ var apiResult =
+ spaceUploadFileRequest.OverwriteExistingFile ?
+ await _lowLevelApiClient.WebFilesPutFileStreamAsync(apiSession, spaceUploadFileRequest.ServerFolder, sendStreamData, onSendProgress, token) :
+ await _lowLevelApiClient.WebFilesPostFileStreamAsync(apiSession, spaceUploadFileRequest.ServerFolder, sendStreamData, onSendProgress, token);
+ FailIfError(apiResult);
+ return Task.FromResult(0);
+
+ }, cancellationToken, OperationType.FileTransfer);
}
}
-
}
+
+
diff --git a/src/Client/MorphServerApiClientGlobalConfig.cs b/src/Client/MorphServerApiClientGlobalConfig.cs
new file mode 100644
index 0000000..bb2d180
--- /dev/null
+++ b/src/Client/MorphServerApiClientGlobalConfig.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Net.Http;
+using System.Security.Cryptography.X509Certificates;
+using System.Net.Security;
+using System.Reflection;
+using System.Net;
+
+namespace Morph.Server.Sdk.Client
+{
+ public static class MorphServerApiClientGlobalConfig
+ {
+
+#if NETSTANDARD2_0
+ private static object obj = new object();
+ public static Func ServerCertificateCustomValidationCallback { get; set; } =
+ (httpRequestMessage, xcert, xchain, sslerror) =>
+ {
+ if (ServicePointManager.ServerCertificateValidationCallback != null)
+ {
+ return ServicePointManager.ServerCertificateValidationCallback(obj, xcert, xchain, sslerror);
+ }
+ else
+ {
+ return false;
+ }
+ };
+#endif
+
+ private const string DefaultClientType = "EMS-SDK";
+
+ public static TimeSpan SessionOpenTimeout { get; set; } = TimeSpan.FromSeconds(30);
+
+ ///
+ /// Default operation execution timeout
+ ///
+ public static TimeSpan OperationTimeout { get; set; } = TimeSpan.FromMinutes(2);
+ ///
+ /// Default File transfer operation timeout
+ ///
+ public static TimeSpan FileTransferTimeout { get; set; } = TimeSpan.FromHours(3);
+
+ ///
+ /// HttpClient Timeout
+ ///
+ public static TimeSpan HttpClientTimeout { get; set; } = TimeSpan.FromHours(24);
+
+ // additional parameter for client identification
+ public static string ClientId { get; set; } = string.Empty;
+ public static string ClientType { get; set; } = DefaultClientType;
+
+ // "Morph.Server.Sdk/x.x.x.x"
+ internal static string SDKVersionString { get; }
+
+ ///
+ /// dispose client when session is closed
+ ///
+ public static bool AutoDisposeClientOnSessionClose { get; set; } = true;
+
+ static MorphServerApiClientGlobalConfig()
+ {
+ // set sdk version string
+ // "Morph.Server.Sdk/x.x.x.x"
+ Assembly thisAssem = typeof(MorphServerApiClientGlobalConfig).Assembly;
+ var assemblyVersion = thisAssem.GetName().Version;
+ SDKVersionString = "Morph.Server.Sdk/" + assemblyVersion.ToString();
+
+ }
+
+
+
+ }
+}
+
+
diff --git a/src/Client/MorphServerAuthenticator.cs b/src/Client/MorphServerAuthenticator.cs
new file mode 100644
index 0000000..77e31eb
--- /dev/null
+++ b/src/Client/MorphServerAuthenticator.cs
@@ -0,0 +1,173 @@
+using Morph.Server.Sdk.Dto;
+using Morph.Server.Sdk.Helper;
+using Morph.Server.Sdk.Model;
+using Morph.Server.Sdk.Model.InternalModels;
+using System;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Morph.Server.Sdk.Client
+{
+ internal static class MorphServerAuthenticator
+ {
+
+ public static async Task OpenSessionMultiplexedAsync(
+ SpaceEnumerationItem desiredSpace,
+ OpenSessionAuthenticatorContext context,
+ OpenSessionRequest openSessionRequest,
+ CancellationToken cancellationToken)
+ {
+ // space access restriction is supported since server 3.9.2
+ // for previous versions api will return SpaceAccessRestriction.NotSupported
+ // a special fall-back mechanize need to be used to open session in such case
+ switch (desiredSpace.SpaceAccessRestriction)
+ {
+ // anon space
+ case SpaceAccessRestriction.None:
+ return ApiSession.Anonymous(context.MorphServerApiClient, openSessionRequest.SpaceName);
+
+ // password protected space
+ case SpaceAccessRestriction.BasicPassword:
+ return await OpenSessionViaSpacePasswordAsync(context, openSessionRequest.SpaceName, openSessionRequest.Password, cancellationToken);
+
+ // windows authentication
+ case SpaceAccessRestriction.WindowsAuthentication:
+ return await OpenSessionViaWindowsAuthenticationAsync(context, openSessionRequest.SpaceName, cancellationToken);
+
+ // fallback
+ case SpaceAccessRestriction.NotSupported:
+
+ // if space is public or password is not set - open anon session
+ if (desiredSpace.IsPublic || string.IsNullOrWhiteSpace(openSessionRequest.Password))
+ {
+ return ApiSession.Anonymous(context.MorphServerApiClient, openSessionRequest.SpaceName);
+ }
+ // otherwise open session via space password
+ else
+ {
+ return await OpenSessionViaSpacePasswordAsync(context, openSessionRequest.SpaceName, openSessionRequest.Password, cancellationToken);
+ }
+
+ default:
+ throw new Exception("Space access restriction method is not supported by this client.");
+ }
+ }
+
+
+ static async Task OpenSessionViaWindowsAuthenticationAsync(OpenSessionAuthenticatorContext context, string spaceName, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(spaceName))
+ {
+ throw new ArgumentException("Space name is not set", nameof(spaceName));
+ }
+ // handler will be disposed automatically
+ HttpClientHandler aHandler = new HttpClientHandler()
+ {
+ ClientCertificateOptions = ClientCertificateOption.Automatic,
+ // required for automatic NTML/Negotiate challenge
+ UseDefaultCredentials = true,
+#if NETSTANDARD2_0
+ ServerCertificateCustomValidationCallback = context.MorphServerApiClient.Config.ServerCertificateCustomValidationCallback
+#endif
+
+
+ };
+
+ // build a new low level client based on specified handler
+ using (var ntmlRestApiClient = context.BuildApiClient(aHandler))
+ {
+ var serverNonce = await internalGetAuthNonceAsync(ntmlRestApiClient, cancellationToken);
+ var token = await internalAuthExternalWindowAsync(ntmlRestApiClient, spaceName, serverNonce, cancellationToken);
+
+ return new ApiSession(context.MorphServerApiClient)
+ {
+ AuthToken = token,
+ IsAnonymous = false,
+ IsClosed = false,
+ SpaceName = spaceName
+ };
+ }
+ }
+ static async Task internalGetAuthNonceAsync(IRestClient apiClient, CancellationToken cancellationToken)
+ {
+ var url = "auth/nonce";
+ var response = await apiClient.PostAsync
+ (url, new GenerateNonceRequestDto(), null, new HeadersCollection(), cancellationToken);
+ response.ThrowIfFailed();
+ return response.Data.Nonce;
+ }
+
+ static async Task internalAuthExternalWindowAsync(IRestClient apiClient, string spaceName, string serverNonce, CancellationToken cancellationToken)
+ {
+ var url = "auth/external/windows";
+ var requestDto = new WindowsExternalLoginRequestDto
+ {
+ RequestToken = serverNonce,
+ SpaceName = spaceName
+ };
+
+ var apiResult = await apiClient.PostAsync(url, requestDto, null, new HeadersCollection(), cancellationToken);
+ apiResult.ThrowIfFailed();
+ return apiResult.Data.Token;
+
+ }
+
+
+
+ ///
+ /// Open a new authenticated session via password
+ ///
+ /// space name
+ /// space password
+ ///
+ ///
+ static async Task OpenSessionViaSpacePasswordAsync(OpenSessionAuthenticatorContext context, string spaceName, string password, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(spaceName))
+ {
+ throw new ArgumentException("Space name is not set.", nameof(spaceName));
+ }
+
+ if (password == null)
+ {
+ throw new ArgumentNullException(nameof(password));
+ }
+
+ var passwordSha256 = CryptographyHelper.CalculateSha256HEX(password);
+ var serverNonceApiResult = await context.LowLevelApiClient.AuthGenerateNonce(cancellationToken);
+ serverNonceApiResult.ThrowIfFailed();
+ var serverNonce = serverNonceApiResult.Data.Nonce;
+ var clientNonce = ConvertHelper.ByteArrayToHexString(CryptographyHelper.GenerateRandomSequence(16));
+ var all = passwordSha256 + serverNonce + clientNonce;
+ var composedHash = CryptographyHelper.CalculateSha256HEX(all);
+
+
+ var requestDto = new LoginRequestDto
+ {
+ ClientSeed = clientNonce,
+ Password = composedHash,
+ Provider = "Space",
+ UserName = spaceName,
+ RequestToken = serverNonce
+ };
+ var authApiResult = await context.LowLevelApiClient.AuthLoginPasswordAsync(requestDto, cancellationToken);
+ authApiResult.ThrowIfFailed();
+ var token = authApiResult.Data.Token;
+
+
+ return new ApiSession(context.MorphServerApiClient)
+ {
+ AuthToken = token,
+ IsAnonymous = false,
+ IsClosed = false,
+ SpaceName = spaceName
+ };
+ }
+
+
+
+ }
+}
+
+
diff --git a/src/Client/MorphServerRestClient.cs b/src/Client/MorphServerRestClient.cs
new file mode 100644
index 0000000..fb9be12
--- /dev/null
+++ b/src/Client/MorphServerRestClient.cs
@@ -0,0 +1,426 @@
+using Morph.Server.Sdk.Exceptions;
+using Morph.Server.Sdk.Helper;
+using System;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Net;
+using System.Collections.Specialized;
+using Morph.Server.Sdk.Mappers;
+using Morph.Server.Sdk.Dto.Errors;
+using System.IO;
+using Morph.Server.Sdk.Model;
+using Morph.Server.Sdk.Model.InternalModels;
+using Morph.Server.Sdk.Dto;
+using Morph.Server.Sdk.Events;
+using static Morph.Server.Sdk.Helper.StreamWithProgress;
+
+namespace Morph.Server.Sdk.Client
+{
+
+
+ public class MorphServerRestClient : IRestClient
+ {
+ private HttpClient httpClient;
+ public HttpClient HttpClient { get => httpClient; set => httpClient = value; }
+
+ public MorphServerRestClient(HttpClient httpClient)
+ {
+ HttpClient = httpClient;
+ }
+ public Task> DeleteAsync(string url, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new()
+ {
+ return SendAsyncApiResult(HttpMethod.Delete, url, null, urlParameters, headersCollection, cancellationToken);
+ }
+
+ public Task> GetAsync(string url, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new()
+ {
+ if (urlParameters == null)
+ {
+ urlParameters = new NameValueCollection();
+ }
+ urlParameters.Add("_", DateTime.Now.Ticks.ToString());
+ return SendAsyncApiResult(HttpMethod.Get, url, null, urlParameters, headersCollection, cancellationToken);
+ }
+
+ public Task> PostAsync(string url, TModel model, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new()
+ {
+ return SendAsyncApiResult(HttpMethod.Post, url, model, urlParameters, headersCollection, cancellationToken);
+ }
+
+ public Task> PutAsync(string url, TModel model, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new()
+ {
+ return SendAsyncApiResult(HttpMethod.Put, url, model, urlParameters, headersCollection, cancellationToken);
+ }
+
+ protected virtual async Task> SendAsyncApiResult(HttpMethod httpMethod, string path, TModel model, NameValueCollection urlParameters, HeadersCollection headersCollection, CancellationToken cancellationToken)
+ where TResult : new()
+ {
+ StringContent stringContent = null;
+ if (model != null)
+ {
+ var serialized = JsonSerializationHelper.Serialize(model);
+ stringContent = new StringContent(serialized, Encoding.UTF8, "application/json");
+ }
+
+ var url = path + (urlParameters != null ? urlParameters.ToQueryString() : string.Empty);
+ var httpRequestMessage = BuildHttpRequestMessage(httpMethod, url, stringContent, headersCollection);
+
+ // for model binding request read and buffer full server response
+ // but for HttpHead content reading is not necessary and might raise error.
+ //var httpCompletionOption = httpMethod != HttpMethod.Head ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead;
+ var httpCompletionOption = HttpCompletionOption.ResponseHeadersRead;
+ using (var response = await httpClient.SendAsync(httpRequestMessage, httpCompletionOption,
+ cancellationToken))
+ {
+ return await HandleResponse(response);
+ }
+ }
+
+ private async Task> HandleResponse(HttpResponseMessage response)
+ where TResult : new()
+ {
+ if (response.IsSuccessStatusCode)
+ {
+ var content = await response.Content.ReadAsStringAsync();
+ var result = JsonSerializationHelper.Deserialize(content);
+ return ApiResult.Ok(result);
+ }
+ else
+ {
+ var error = await BuildExceptionFromResponse(response);
+ return ApiResult.Fail(error);
+ }
+ }
+
+ protected HttpRequestMessage BuildHttpRequestMessage(HttpMethod httpMethod, string url, HttpContent content, HeadersCollection headersCollection)
+ {
+ var requestMessage = new HttpRequestMessage()
+ {
+ Content = content,
+ Method = httpMethod,
+ RequestUri = new Uri(url, UriKind.Relative)
+ };
+ if (headersCollection != null)
+ {
+ headersCollection.Fill(requestMessage.Headers);
+ }
+ return requestMessage;
+ }
+
+
+
+ private async Task BuildExceptionFromResponse(HttpResponseMessage response)
+ {
+
+ var rawContent = await response.Content.ReadAsStringAsync();
+ if (!string.IsNullOrWhiteSpace(rawContent))
+ {
+ ErrorResponse errorResponse = null;
+ try
+ {
+ errorResponse = DeserializeErrorResponse(rawContent);
+ }
+ catch (Exception)
+ {
+ return new ResponseParseException("An error occurred while deserializing the response", rawContent);
+ }
+ if (errorResponse.error == null)
+ return new ResponseParseException("An error occurred while deserializing the response", rawContent);
+
+ switch (errorResponse.error.code)
+ {
+ case ReadableErrorTopCode.Conflict: return new MorphApiConflictException(errorResponse.error.message);
+ case ReadableErrorTopCode.NotFound: return new MorphApiNotFoundException(errorResponse.error.message);
+ case ReadableErrorTopCode.Forbidden: return new MorphApiForbiddenException(errorResponse.error.message);
+ case ReadableErrorTopCode.Unauthorized: return new MorphApiUnauthorizedException(errorResponse.error.message);
+ case ReadableErrorTopCode.BadArgument: return new MorphApiBadArgumentException(FieldErrorsMapper.MapFromDto(errorResponse.error), errorResponse.error.message);
+ default: return BuildCustomExceptionFromErrorResponse(rawContent, errorResponse);
+ }
+ }
+
+ else
+ {
+ switch (response.StatusCode)
+ {
+ case HttpStatusCode.Conflict: return new MorphApiConflictException(response.ReasonPhrase ?? "Conflict");
+ case HttpStatusCode.NotFound: return new MorphApiNotFoundException(response.ReasonPhrase ?? "Not found");
+ case HttpStatusCode.Forbidden: return new MorphApiForbiddenException(response.ReasonPhrase ?? "Forbidden");
+ case HttpStatusCode.Unauthorized: return new MorphApiUnauthorizedException(response.ReasonPhrase ?? "Unauthorized");
+ case HttpStatusCode.BadRequest: return new MorphClientGeneralException("Unknown", response.ReasonPhrase ?? "Unknown error");
+ default: return new ResponseParseException(response.ReasonPhrase, null);
+ }
+
+ }
+ }
+
+ protected virtual ErrorResponse DeserializeErrorResponse(string rawContent)
+ {
+ return JsonSerializationHelper.Deserialize(rawContent);
+ }
+
+ protected virtual Exception BuildCustomExceptionFromErrorResponse(string rawContent, ErrorResponse errorResponse)
+ {
+ return new MorphClientGeneralException(errorResponse.error.code, errorResponse.error.message);
+ }
+
+ public void Dispose()
+ {
+ if (HttpClient != null)
+ {
+ HttpClient.Dispose();
+ HttpClient = null;
+ }
+ }
+
+
+
+
+ public Task> PushContiniousStreamingDataAsync(
+ HttpMethod httpMethod, string path, ContiniousStreamingRequest startContiniousStreamingRequest, NameValueCollection urlParameters, HeadersCollection headersCollection,
+ CancellationToken cancellationToken)
+ where TResult : new()
+ {
+ try
+ {
+ string boundary = "MorphRestClient-Streaming--------" + Guid.NewGuid().ToString("N");
+
+ var content = new MultipartFormDataContent(boundary);
+
+
+ var streamContent = new ContiniousSteamingHttpContent(cancellationToken);
+ var serverPushStreaming = new ServerPushStreaming(streamContent);
+ content.Add(streamContent, "files", Path.GetFileName(startContiniousStreamingRequest.FileName));
+ var url = path + (urlParameters != null ? urlParameters.ToQueryString() : string.Empty);
+ var requestMessage = BuildHttpRequestMessage(httpMethod, url, content, headersCollection);
+ //using (requestMessage)
+ {
+ new Task(async () =>
+ {
+ try
+ {
+ try
+ {
+ var response = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
+
+ var result = await HandleResponse(response);
+ serverPushStreaming.SetApiResult(result);
+ response.Dispose();
+ }
+ catch (Exception ex) when (ex.InnerException != null &&
+ ex.InnerException is WebException web &&
+ web.Status == WebExceptionStatus.ConnectionClosed)
+ {
+ serverPushStreaming.SetApiResult(ApiResult.Fail(new MorphApiNotFoundException("Specified folder not found")));
+ }
+ catch (Exception e)
+ {
+ serverPushStreaming.SetApiResult(ApiResult.Fail(e));
+ }
+
+ requestMessage.Dispose();
+ streamContent.Dispose();
+ content.Dispose();
+ }
+ catch (Exception)
+ {
+ // dd
+ }
+
+ }).Start();
+ return Task.FromResult(ApiResult.Ok(serverPushStreaming));
+
+
+ }
+
+
+ }
+ catch (Exception ex) when (ex.InnerException != null &&
+ ex.InnerException is WebException web &&
+ web.Status == WebExceptionStatus.ConnectionClosed)
+ {
+ return Task.FromResult(ApiResult.Fail(new MorphApiNotFoundException("Specified folder not found")));
+ }
+ catch (Exception e)
+ {
+ return Task.FromResult(ApiResult.Fail(e));
+ }
+ }
+
+
+ public async Task> SendFileStreamAsync(
+ HttpMethod httpMethod, string path, SendFileStreamData sendFileStreamData,
+ NameValueCollection urlParameters, HeadersCollection headersCollection,
+ Action onSendProgress,
+ CancellationToken cancellationToken)
+ where TResult : new()
+ {
+ try
+ {
+ string boundary = "MorphRestClient--------" + Guid.NewGuid().ToString("N");
+
+ using (var content = new MultipartFormDataContent(boundary))
+ {
+ var uploadProgress = new FileProgress(sendFileStreamData.FileName, sendFileStreamData.FileSize, onSendProgress);
+
+ using (cancellationToken.Register(() => uploadProgress.ChangeState(FileProgressState.Cancelled)))
+ {
+ using (var streamContent = new ProgressStreamContent(sendFileStreamData.Stream, uploadProgress))
+ {
+ content.Add(streamContent, "files", Path.GetFileName(sendFileStreamData.FileName));
+ var url = path + (urlParameters != null ? urlParameters.ToQueryString() : string.Empty);
+ var requestMessage = BuildHttpRequestMessage(httpMethod, url, content, headersCollection);
+ using (requestMessage)
+ {
+ using (var response = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
+ {
+ return await HandleResponse(response);
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex) when (ex.InnerException != null &&
+ ex.InnerException is WebException web &&
+ web.Status == WebExceptionStatus.ConnectionClosed)
+ {
+ return ApiResult.Fail(new MorphApiNotFoundException("Specified folder not found"));
+ }
+ catch (Exception e)
+ {
+ return ApiResult.Fail(e);
+ }
+ }
+
+ protected async Task> RetrieveFileStreamAsync(HttpMethod httpMethod, string path, NameValueCollection urlParameters, HeadersCollection headersCollection, Action onReceiveProgress, CancellationToken cancellationToken)
+ {
+ var url = path + (urlParameters != null ? urlParameters.ToQueryString() : string.Empty);
+ HttpResponseMessage response = await httpClient.SendAsync(
+ BuildHttpRequestMessage(httpMethod, url, null, headersCollection), HttpCompletionOption.ResponseHeadersRead, cancellationToken);
+ {
+ if (response.IsSuccessStatusCode)
+ {
+ var contentDisposition = response.Content.Headers.ContentDisposition;
+ // need to fix double quotes, that may come from server response
+ // FileNameStar contains file name encoded in UTF8
+ var realFileName = (contentDisposition.FileNameStar ?? contentDisposition.FileName).TrimStart('\"').TrimEnd('\"');
+ var contentLength = response.Content.Headers.ContentLength;
+ if (!contentLength.HasValue)
+ {
+ throw new Exception("Response content length header is not set by the server.");
+ }
+
+ FileProgress downloadProgress = null;
+
+ if (contentLength.HasValue)
+ {
+ downloadProgress = new FileProgress(realFileName, contentLength.Value, onReceiveProgress);
+ }
+ downloadProgress?.ChangeState(FileProgressState.Starting);
+ long totalProcessedBytes = 0;
+
+ {
+ // stream must be disposed by a caller
+ Stream streamToReadFrom = await response.Content.ReadAsStreamAsync();
+
+
+ var streamWithProgress = new StreamWithProgress(streamToReadFrom, contentLength.Value, cancellationToken,
+ e =>
+ {
+ // on read progress handler
+ if (downloadProgress != null)
+ {
+ totalProcessedBytes = e.TotalBytesRead;
+ downloadProgress.SetProcessedBytes(totalProcessedBytes);
+ }
+ },
+ () =>
+ {
+ // on disposed handler
+ if (downloadProgress != null && downloadProgress.ProcessedBytes != totalProcessedBytes)
+ {
+ downloadProgress.ChangeState(FileProgressState.Cancelled);
+ }
+ response.Dispose();
+ },
+ (tokenCancellationReason, token) =>
+ {
+ // on tokenCancelled
+ if (tokenCancellationReason == TokenCancellationReason.HttpTimeoutToken)
+ {
+ throw new Exception("Timeout");
+ }
+ if(tokenCancellationReason == TokenCancellationReason.OperationCancellationToken)
+ {
+ throw new OperationCanceledException(token);
+ }
+
+ });
+ return ApiResult.Ok(new FetchFileStreamData(streamWithProgress, realFileName, contentLength));
+
+ }
+ }
+ else
+ {
+ try
+ {
+ var error = await BuildExceptionFromResponse(response);
+ return ApiResult.Fail(error);
+ }
+ finally
+ {
+ response.Dispose();
+ }
+ }
+ }
+ }
+
+
+ public Task> PutFileStreamAsync(string url, SendFileStreamData sendFileStreamData, NameValueCollection urlParameters, HeadersCollection headersCollection, Action onSendProgress, CancellationToken cancellationToken)
+ where TResult : new()
+ {
+ return SendFileStreamAsync(HttpMethod.Put, url, sendFileStreamData, urlParameters, headersCollection, onSendProgress, cancellationToken);
+ }
+
+ public Task> PostFileStreamAsync(string url, SendFileStreamData sendFileStreamData, NameValueCollection urlParameters, HeadersCollection headersCollection, Action onSendProgress, CancellationToken cancellationToken)
+ where TResult : new()
+ {
+ return SendFileStreamAsync(HttpMethod.Post, url, sendFileStreamData, urlParameters, headersCollection, onSendProgress, cancellationToken);
+ }
+
+
+ public Task