From 18f83cf2f962ecc95e50b23de1b08e0b8105c5bf Mon Sep 17 00:00:00 2001 From: Emanuel Albu <42834028+ealbu@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:20:27 +0300 Subject: [PATCH] LanguageWeaver 2.0.2.1: SDLCOM-6140 - Language Weaver Provider plugin throws an error after Studio was closed for several hours: "Authentication failed. Operation is not valid due to the current state of the object." * Add logging * Add more details to the exception message shown --- .../ApplicationInitializer.cs | 5 +- .../LanguageWeaverProvider.csproj | 3 + LanguageWeaverProvider/Log.cs | 38 + .../Model/CloudCredentials.cs | 25 +- .../Properties/AssemblyInfo.cs | 2 +- .../Services/CloudService.cs | 710 +++++++++--------- .../pluginpackage.manifest.xml | 2 +- 7 files changed, 423 insertions(+), 362 deletions(-) create mode 100644 LanguageWeaverProvider/Log.cs diff --git a/LanguageWeaverProvider/ApplicationInitializer.cs b/LanguageWeaverProvider/ApplicationInitializer.cs index c6af11457c..bb464c9da2 100644 --- a/LanguageWeaverProvider/ApplicationInitializer.cs +++ b/LanguageWeaverProvider/ApplicationInitializer.cs @@ -25,11 +25,12 @@ public class ApplicationInitializer : IApplicationInitializer public static IDictionary TranslationOptions { get; set; } public void Execute() - { + { RatedSegments = new List(); TranslationOptions = new Dictionary(); CurrentAppVersion = GetAssemblyFileVersion(); - } + Log.Setup(); + } public static Window GetBatchTaskWindow() { diff --git a/LanguageWeaverProvider/LanguageWeaverProvider.csproj b/LanguageWeaverProvider/LanguageWeaverProvider.csproj index acd0aca98a..7a2d1c7b70 100644 --- a/LanguageWeaverProvider/LanguageWeaverProvider.csproj +++ b/LanguageWeaverProvider/LanguageWeaverProvider.csproj @@ -87,6 +87,9 @@ $(TradosFolder)\Newtonsoft.Json.dll + + $(TradosFolder)\NLog.dll + $(TradosFolder)\Sdl.Core.Globalization.dll diff --git a/LanguageWeaverProvider/Log.cs b/LanguageWeaverProvider/Log.cs new file mode 100644 index 0000000000..4ffec1a3ac --- /dev/null +++ b/LanguageWeaverProvider/Log.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; +using System.Text; +using NLog; +using NLog.Config; +using NLog.Targets; + +namespace LanguageWeaverProvider +{ + public static class Log + { + public static void Setup() + { + LogManager.Configuration ??= new LoggingConfiguration(); + + var config = LogManager.Configuration; + + var logDirectoryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Trados AppStore", + "Language Weaver", "Logs"); + Directory.CreateDirectory(logDirectoryPath); + + var target = new FileTarget + { + Name = "LanguageWeaver", + FileName = Path.Combine(logDirectoryPath, "LanguageWeaverProvider.Logs.txt"), + ArchiveEvery = FileArchivePeriod.Day, + ArchiveNumbering = ArchiveNumberingMode.Date, + Encoding = Encoding.UTF8, + Layout = "${logger}: ${longdate} ${level} ${message} ${exception}" + }; + + config.AddTarget(target); + config.AddRuleForAllLevels(target, "*LanguageWeaverProvider*"); + + LogManager.ReconfigExistingLoggers(); + } + } +} diff --git a/LanguageWeaverProvider/Model/CloudCredentials.cs b/LanguageWeaverProvider/Model/CloudCredentials.cs index 436b564b34..5cc5143be5 100644 --- a/LanguageWeaverProvider/Model/CloudCredentials.cs +++ b/LanguageWeaverProvider/Model/CloudCredentials.cs @@ -1,19 +1,24 @@ namespace LanguageWeaverProvider.Model { - public class CloudCredentials + public class CloudCredentials { - public string AccountId { get; set; } + public string AccountId { get; set; } - public string AccountRegion { get; set; } + public string AccountRegion { get; set; } - public string UserName { get; set; } + public string ClientID { get; set; } + public string ClientSecret { get; set; } + public string ConnectionCode { get; set; } + public string UserName { get; set; } - public string UserPassword { get; set; } + public string UserPassword { get; set; } - public string ClientID { get; set; } - - public string ClientSecret { get; set; } - - public string ConnectionCode { get; set; } + public override string ToString() + { + var password = string.IsNullOrWhiteSpace(UserPassword) ? "NULL" : "PRESENT"; + var clientSecret = string.IsNullOrWhiteSpace(ClientSecret) ? "NULL" : "PRESENT"; + return $"AccountId: {AccountId}, AccountRegion: {AccountRegion}, UserName: {UserName}, " + + $"ClientID: {ClientID}, Password: {password}, ClientSecret: {clientSecret}, ConnectionCode: {ConnectionCode}"; + } } } \ No newline at end of file diff --git a/LanguageWeaverProvider/Properties/AssemblyInfo.cs b/LanguageWeaverProvider/Properties/AssemblyInfo.cs index cb01db48e2..717ea3b63a 100644 --- a/LanguageWeaverProvider/Properties/AssemblyInfo.cs +++ b/LanguageWeaverProvider/Properties/AssemblyInfo.cs @@ -29,4 +29,4 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyFileVersion("2.0.2.0")] \ No newline at end of file +[assembly: AssemblyFileVersion("2.0.2.1")] \ No newline at end of file diff --git a/LanguageWeaverProvider/Services/CloudService.cs b/LanguageWeaverProvider/Services/CloudService.cs index 010930304b..43ee0905f4 100644 --- a/LanguageWeaverProvider/Services/CloudService.cs +++ b/LanguageWeaverProvider/Services/CloudService.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net.Http; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Web; using LanguageWeaverProvider.Extensions; @@ -14,355 +15,368 @@ using LanguageWeaverProvider.XliffConverter.Converter; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NLog; namespace LanguageWeaverProvider.Services { - public static class CloudService - { - public static async Task AuthenticateSSOUser(ITranslationOptions translationOptions, CloudAuth0Config auth0Config, Uri uri, string selectedRegion) - { - try - { - var uriParams = uri.PathAndQuery.TrimStart('/'); - var parameters = HttpUtility.ParseQueryString(uriParams); - var param = HttpUtility.ParseQueryString(uriParams).AllKeys.ToDictionary(x => x, x => parameters[x]); - param["client_id"] = auth0Config.ClientId; - param["redirect_uri"] = auth0Config.RedirectUri; - param["code_verifier"] = auth0Config.CodeVerifier; - param["grant_type"] = "authorization_code"; - - var requestUri = new Uri("https://sdl-prod.eu.auth0.com/oauth/token"); - var formUrlEncodedContent = new FormUrlEncodedContent(param); - using var httpRequest = new HttpRequestMessage() - { - Method = HttpMethod.Post, - RequestUri = requestUri, - Content = formUrlEncodedContent - }; - - var result = await GetHttpClient().SendAsync(httpRequest); - var content = result.Content.ReadAsStringAsync().Result; - //var x = await Service.SendRequest(HttpMethod.Post, formUrlEncodedContent, content: content); - if (!result.IsSuccessStatusCode) - { - var errorResponse = JsonConvert.DeserializeObject(content); - ErrorHandling.ShowDialog(null, $"{result.StatusCode} {(int)result.StatusCode}", errorResponse.ToString()); - return false; - } - - var ssoToken = JsonConvert.DeserializeObject(content); - translationOptions.AccessToken = new AccessToken - { - Token = ssoToken.AccessToken, - TokenType = ssoToken.TokenType, - RefreshToken = ssoToken.RefreshToken, - ExpiresAt = (long)(DateTime.UtcNow.AddSeconds(ssoToken.ExpiresIn) - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalMilliseconds, - BaseUri = new Uri(selectedRegion) - }; - - await SetAccountId(translationOptions, selectedRegion); - return true; - } - catch (Exception ex) - { - ex.ShowDialog("Authentication failed", ex.Message, true); - return false; - } - } - - public static async Task AuthenticateUser(ITranslationOptions translationOptions, AuthenticationType authenticationType) - { - try - { - var cloudCredentials = translationOptions.CloudCredentials; - var response = await Authenticate(cloudCredentials, authenticationType); - if (response.IsSuccessStatusCode) - { - translationOptions.AccessToken = await response.DeserializeResponse(); - translationOptions.AccessToken.BaseUri = new Uri(cloudCredentials.AccountRegion); - await SetAccountId(translationOptions, cloudCredentials.AccountRegion, cloudCredentials); - return true; - } - - var cloudAccountError = await response.DeserializeResponse("status"); - ErrorHandling.ShowDialog(null, $"{response.StatusCode} {(int)response.StatusCode}", $"Code: {cloudAccountError.Code}\nMessage: {cloudAccountError.Description}"); - return false; - } - catch (Exception ex) - { - ex.ShowDialog("Authentication failed", ex.Message, true); - return false; - } - } - - private static async Task SetAccountId(ITranslationOptions translationOptions, string uri, CloudCredentials cloudCredentials = null) - { - var requesturi = translationOptions.AuthenticationType switch - { - AuthenticationType.CloudCredentials => $"{uri}v4/accounts/users/self", - AuthenticationType.CloudAPI => $"{uri}v4/accounts/api-credentials/self", - AuthenticationType.CloudSSO => $"{uri}v4/accounts/users/self", - }; - - var accountId = await GetUserInfo(translationOptions.AccessToken, requesturi, "accountId"); - translationOptions.AccessToken.AccountId = accountId; - if (cloudCredentials is not null) - { - cloudCredentials.AccountId = accountId; - } - } - - - private static async Task Authenticate(CloudCredentials cloudCredentials, AuthenticationType authenticationType) - { - var requesturi = authenticationType switch - { - AuthenticationType.CloudCredentials => $"{cloudCredentials.AccountRegion}v4/token/user", - AuthenticationType.CloudAPI => $"{cloudCredentials.AccountRegion}v4/token" - }; - - var content = GetAuthenticationContent(cloudCredentials, authenticationType); - var stringContent = new StringContent(content, null, "application/json"); - var response = await Service.SendRequest(HttpMethod.Post, requesturi, content: stringContent); - return response; - } - - private static string GetAuthenticationContent(CloudCredentials cloudCredentials, AuthenticationType authenticationType) - { - var (idKey, idValue,secretKey, secretValue) = authenticationType switch - { - AuthenticationType.CloudCredentials => ("username", cloudCredentials.UserName, "password", cloudCredentials.UserPassword), - AuthenticationType.CloudAPI => ("clientId", cloudCredentials.ClientID, "clientSecret", cloudCredentials.ClientSecret) - }; - - return $"\r\n{{\r\n \"{idKey}\": \"{idValue}\",\r\n \"{secretKey}\": \"{secretValue}\"\r\n}}"; - } - - private static async Task GetUserInfo(AccessToken accessToken, string requestUri, string property) - { - var response = await Service.SendRequest(HttpMethod.Get, requestUri, accessToken); - var accountId = await Service.DeserializeResponse(response, property); - return accountId; - } - - public static async Task> GetResources(AccessToken accessToken, CloudResources resource) - { - const string LanguagePairsEndPoint = "subscriptions/language-pairs"; - const string LanguagePairsProperty = "languagePairs"; - const string DictionariesEndPoint = "dictionaries"; - const string DictionariesProperty = "dictionaries"; - - var (resourceRequested, property) = resource switch - { - CloudResources.LanguagePairs => (LanguagePairsEndPoint, LanguagePairsProperty), - CloudResources.Dictionaries => (DictionariesEndPoint, DictionariesProperty), - _ => throw new ArgumentException("Unsupported Resource value") - }; - - var requestUri = $"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}/{resourceRequested}"; - var response = await Service.SendRequest(HttpMethod.Get, requestUri, accessToken); - var languagePairs = await Service.DeserializeResponse>(response, property); - return languagePairs; - } - - public static async Task Translate(AccessToken accessToken, PairMapping mappedPair, Xliff sourceXliff) - { - try - { - var translationRequest = await SendTranslationRequest(accessToken, mappedPair, sourceXliff); - await WaitForTranslationCompletion(accessToken, translationRequest.RequestId); - var translation = await GetTranslationInfo(accessToken, translationRequest.RequestId, "content"); - var translatedSegment = translation.Translation.First(); - var translatedXliffSegment = Converter.ParseXliffString(translatedSegment); - return translatedXliffSegment; - } - catch (Exception ex) - { - throw ex; - } - } - - private static async Task SendTranslationRequest(AccessToken accessToken, PairMapping mappedPair, Xliff sourceXliff) - { - var requestUri = $"{accessToken.BaseUri}v4/mt/translations/async"; - var translationRequestModel = CreateTranslationRequest(mappedPair, sourceXliff); - var translationRequestModelJson = JsonConvert.SerializeObject(translationRequestModel); - var content = new StringContent(translationRequestModelJson, Encoding.UTF8, "application/json"); - var response = await Service.SendRequest(HttpMethod.Post, requestUri, accessToken, content); - var translationRequestResponse = await Service.DeserializeResponse(response); - return translationRequestResponse; - } - - private static CloudTranslationRequest CreateTranslationRequest(PairMapping mappedPair, Xliff sourceXliff) - { - const string InputFormat = "xliff"; - - var linguisticOptionsDictionary = mappedPair.LinguisticOptions?.ToDictionary(lo => lo.Id, lo => lo.SelectedValue); - var dictionaries = mappedPair.Dictionaries.Where(d => d.IsSelected).Select(d => d.DictionaryId).ToArray(); - - var translationRequestModel = new CloudTranslationRequest - { - SourceLanguageId = mappedPair.SourceCode, - TargetLanguageId = mappedPair.TargetCode, - Input = [sourceXliff.ToString()], - Model = mappedPair.SelectedModel.Model, - InputFormat = InputFormat, - Dictionaries = dictionaries, - LinguisticOptions = linguisticOptionsDictionary, - QualityEstimation = mappedPair.SelectedModel.QeSupport ? 1 : 0 - }; - - return translationRequestModel; - } - - private static async Task WaitForTranslationCompletion(AccessToken accessToken, string RequestId) - { - CloudTranslationStatus translationStatus; - bool isWaiting; - do - { - translationStatus = await GetTranslationInfo(accessToken, RequestId); - isWaiting = translationStatus.Status.Equals("init", StringComparison.OrdinalIgnoreCase) - || translationStatus.Status.Equals("translating", StringComparison.OrdinalIgnoreCase); - if (isWaiting) - { - await Task.Delay(1000); - } - } while (isWaiting); - } - - private static async Task GetTranslationInfo(AccessToken accessToken, string requestId, string endpoint = null) - { - var requestUri = $"{accessToken.BaseUri}v4/mt/translations/async/{requestId}/{endpoint}"; - var translationStatusReponse = await Service.SendRequest(HttpMethod.Get, requestUri, accessToken); - var x = await translationStatusReponse.Content.ReadAsStringAsync(); - var translationStatus = await Service.DeserializeResponse(translationStatusReponse); - return translationStatus; - } - - public static async Task CreateFeedback(AccessToken accessToken, FeedbackRequest feedbackRequest) - { - try - { - var requestUri = $"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}/feedback/translations"; - var feedbackRequestJson = JsonConvert.SerializeObject(feedbackRequest); - var content = new StringContent(feedbackRequestJson, new UTF8Encoding(), "application/json"); - var response = await Service.SendRequest(HttpMethod.Post, requestUri, accessToken, content); - response.EnsureSuccessStatusCode(); - return response.IsSuccessStatusCode; - } - catch (Exception ex) - { - ErrorHandling.ShowDialog(ex, "Feedback", ex.Message, true); - return false; - } - } - - public static async Task CreateDictionaryTerm(AccessToken accessToken, PairDictionary pairDictionary, DictionaryTerm newDictionaryTerm) - { - var requestUri = $"https://api.languageweaver.com/v4/accounts/{accessToken.AccountId}/dictionaries/{pairDictionary.DictionaryId}/terms"; - var content = JsonConvert.SerializeObject(newDictionaryTerm); - var stringContent = new StringContent(content, new UTF8Encoding(), "application/json"); - - var response = await Service.SendRequest(HttpMethod.Post, requestUri, accessToken, stringContent); - var isSuccessStatusCode = response.IsSuccessStatusCode; - if (isSuccessStatusCode) - { - return isSuccessStatusCode; - } - - var errors = await Service.DeserializeResponse(response); - var error = errors.Errors.FirstOrDefault(); - ErrorHandling.ShowDialog(null, $"Code {error?.Code}", error?.Description); - return isSuccessStatusCode; - } - - public static HttpClient GetHttpClient() - { - var httpClient = new HttpClient(); - httpClient.DefaultRequestHeaders.Add(Constants.TraceAppKey, Constants.TraceAppValue); - httpClient.DefaultRequestHeaders.Add(Constants.TraceAppVersionKey, ApplicationInitializer.CurrentAppVersion); - return httpClient; - } - - #region Account subscription - ON HOLD - public static async Task GetAccountDetails(AccessToken accessToken) - { - var uri = new Uri($"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}/subscriptions"); - var request = new HttpRequestMessage(HttpMethod.Get, uri); - request.Headers.Add("Authorization", $"{accessToken.TokenType} {accessToken.Token}"); - var response = await GetHttpClient().SendAsync(request); - var responseContent = await response.Content.ReadAsStringAsync(); - - var output = JsonConvert.DeserializeObject(responseContent); - return output; - } - - public static async Task> GetSubscriptionDetails(AccessToken accessToken) - { - var uri = new Uri($"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}"); - var request = new HttpRequestMessage(HttpMethod.Get, uri); - request.Headers.Add("Authorization", $"{accessToken.TokenType} {accessToken.Token}"); - var response = await GetHttpClient().SendAsync(request); - var responseContent = await response.Content.ReadAsStringAsync(); - - var accountCategoryFeatures = JObject.Parse(responseContent)["accountSetting"]["accountCategoryFeatures"].ToObject>(); - return accountCategoryFeatures; - } - - public static async Task GetUsageReport(AccessToken accessToken, IEnumerable subscriptions) - { - var usageReport = new CloudUsageReport(); - foreach (var subscription in subscriptions.Where(sub => sub.IsActive)) - { - var startDate = DateTime.ParseExact(subscription.StartDate, "yyyy/MM/dd", null); - var endDate = DateTime.ParseExact(subscription.EndDate, "yyyy/MM/dd", null); - - for (var currentMonthStart = startDate; currentMonthStart < endDate; currentMonthStart = currentMonthStart.AddMonths(3)) - { - var currentMonthEnd = currentMonthStart.AddMonths(2); - if (currentMonthEnd > endDate) - { - currentMonthEnd = endDate; - } - - var uri = $"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}/reports/usage/translations"; - var request = new HttpRequestMessage(HttpMethod.Get, uri); - request.Headers.Add("Authorization", $"{accessToken.TokenType} {accessToken.Token}"); - - var period = new CloudSubscriptionPeriod - { - StartDate = currentMonthStart.ToString("yyyy/MM/dd", CultureInfo.InvariantCulture), - EndDate = currentMonthEnd.ToString("yyyy/MM/dd", CultureInfo.InvariantCulture) - }; - - var feedbackRequestJson = JsonConvert.SerializeObject(period); - var content = new StringContent(feedbackRequestJson, new UTF8Encoding(), "application/json"); - - var response = await Service.SendRequest(HttpMethod.Post, uri, accessToken, content); - var responseContent = await response.Content.ReadAsStringAsync(); - - var currentPeriodUsageReports = JsonConvert.DeserializeObject(responseContent); - - foreach (var currentPeriodUsageReport in currentPeriodUsageReports.Reports) - { - UpdateUsageReport(usageReport, currentPeriodUsageReport); - } - } - } - - return usageReport; - } - - private static void UpdateUsageReport(CloudUsageReport totalUsageReport, CloudUsageReport currentPeriodReport) - { - totalUsageReport.OutputWordCount += currentPeriodReport.OutputWordCount; - totalUsageReport.OutputCharCount += currentPeriodReport.OutputCharCount; - totalUsageReport.Count += currentPeriodReport.Count; - totalUsageReport.InputWordCount += currentPeriodReport.InputWordCount; - totalUsageReport.InputCharCount += currentPeriodReport.InputCharCount; - // Add more properties if needed - } - #endregion - } + public static class CloudService + { + private static Logger _logger = LogManager.GetCurrentClassLogger(); + + public static async Task AuthenticateSSOUser(ITranslationOptions translationOptions, CloudAuth0Config auth0Config, Uri uri, string selectedRegion) + { + try + { + var uriParams = uri.PathAndQuery.TrimStart('/'); + var parameters = HttpUtility.ParseQueryString(uriParams); + var param = HttpUtility.ParseQueryString(uriParams).AllKeys.ToDictionary(x => x, x => parameters[x]); + param["client_id"] = auth0Config.ClientId; + param["redirect_uri"] = auth0Config.RedirectUri; + param["code_verifier"] = auth0Config.CodeVerifier; + param["grant_type"] = "authorization_code"; + + var requestUri = new Uri("https://sdl-prod.eu.auth0.com/oauth/token"); + var formUrlEncodedContent = new FormUrlEncodedContent(param); + using var httpRequest = new HttpRequestMessage() + { + Method = HttpMethod.Post, + RequestUri = requestUri, + Content = formUrlEncodedContent + }; + + var result = await GetHttpClient().SendAsync(httpRequest); + var content = result.Content.ReadAsStringAsync().Result; + //var x = await Service.SendRequest(HttpMethod.Post, formUrlEncodedContent, content: content); + if (!result.IsSuccessStatusCode) + { + var errorResponse = JsonConvert.DeserializeObject(content); + ErrorHandling.ShowDialog(null, $"{result.StatusCode} {(int)result.StatusCode}", errorResponse.ToString()); + return false; + } + + var ssoToken = JsonConvert.DeserializeObject(content); + translationOptions.AccessToken = new AccessToken + { + Token = ssoToken.AccessToken, + TokenType = ssoToken.TokenType, + RefreshToken = ssoToken.RefreshToken, + ExpiresAt = (long)(DateTime.UtcNow.AddSeconds(ssoToken.ExpiresIn) - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalMilliseconds, + BaseUri = new Uri(selectedRegion) + }; + + await SetAccountId(translationOptions, selectedRegion); + return true; + } + catch (Exception ex) + { + ex.ShowDialog("Authentication failed", ex.Message, true); + return false; + } + } + + public static async Task AuthenticateUser(ITranslationOptions translationOptions, AuthenticationType authenticationType) + { + try + { + var cloudCredentials = translationOptions.CloudCredentials; + var response = await Authenticate(cloudCredentials, authenticationType); + if (response.IsSuccessStatusCode) + { + _logger.Log(LogLevel.Info, "Authentication successful."); + + translationOptions.AccessToken = await response.DeserializeResponse(); + translationOptions.AccessToken.BaseUri = new Uri(cloudCredentials.AccountRegion); + await SetAccountId(translationOptions, cloudCredentials.AccountRegion, cloudCredentials); + return true; + } + + _logger.Log(LogLevel.Info, "Authentication unsuccessful."); + var cloudAccountError = await response.DeserializeResponse("status"); + ErrorHandling.ShowDialog(null, $"{response.StatusCode} {(int)response.StatusCode}", + $"Code: {cloudAccountError.Code}\nMessage: {cloudAccountError.Description}"); + return false; + } + catch (Exception ex) + { + var message = $"{ex.Message}. {Environment.StackTrace}."; + _logger.Log(LogLevel.Error, message); + ex.ShowDialog("Authentication failed", message, true); + return false; + } + } + + private static async Task SetAccountId(ITranslationOptions translationOptions, string uri, CloudCredentials cloudCredentials = null) + { + var requesturi = translationOptions.AuthenticationType switch + { + AuthenticationType.CloudCredentials => $"{uri}v4/accounts/users/self", + AuthenticationType.CloudAPI => $"{uri}v4/accounts/api-credentials/self", + AuthenticationType.CloudSSO => $"{uri}v4/accounts/users/self", + }; + + var accountId = await GetUserInfo(translationOptions.AccessToken, requesturi, "accountId"); + translationOptions.AccessToken.AccountId = accountId; + if (cloudCredentials is not null) + { + cloudCredentials.AccountId = accountId; + } + } + + private static async Task Authenticate(CloudCredentials cloudCredentials, AuthenticationType authenticationType) + { + _logger.Log(LogLevel.Info, + $"[Authenticate] start - CloudCredentials: {cloudCredentials}, AuthenticationType: {authenticationType}"); + + var requestUri = authenticationType switch + { + AuthenticationType.CloudCredentials => $"{cloudCredentials.AccountRegion}v4/token/user", + AuthenticationType.CloudAPI => $"{cloudCredentials.AccountRegion}v4/token", + _ => throw new ArgumentOutOfRangeException(nameof(authenticationType), authenticationType, "Unsupported authentication type.") + }; + + var content = GetAuthenticationContent(cloudCredentials, authenticationType); + var stringContent = new StringContent(content, null, "application/json"); + + var response = await Service.SendRequest(HttpMethod.Post, requestUri, content: stringContent); + return response; + } + + private static string GetAuthenticationContent(CloudCredentials cloudCredentials, AuthenticationType authenticationType) + { + var (idKey, idValue, secretKey, secretValue) = authenticationType switch + { + AuthenticationType.CloudCredentials => ("username", cloudCredentials.UserName, "password", cloudCredentials.UserPassword), + AuthenticationType.CloudAPI => ("clientId", cloudCredentials.ClientID, "clientSecret", cloudCredentials.ClientSecret) + }; + + return $"\r\n{{\r\n \"{idKey}\": \"{idValue}\",\r\n \"{secretKey}\": \"{secretValue}\"\r\n}}"; + } + + private static async Task GetUserInfo(AccessToken accessToken, string requestUri, string property) + { + var response = await Service.SendRequest(HttpMethod.Get, requestUri, accessToken); + var accountId = await Service.DeserializeResponse(response, property); + return accountId; + } + + public static async Task> GetResources(AccessToken accessToken, CloudResources resource) + { + const string LanguagePairsEndPoint = "subscriptions/language-pairs"; + const string LanguagePairsProperty = "languagePairs"; + const string DictionariesEndPoint = "dictionaries"; + const string DictionariesProperty = "dictionaries"; + + var (resourceRequested, property) = resource switch + { + CloudResources.LanguagePairs => (LanguagePairsEndPoint, LanguagePairsProperty), + CloudResources.Dictionaries => (DictionariesEndPoint, DictionariesProperty), + _ => throw new ArgumentException("Unsupported Resource value") + }; + + var requestUri = $"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}/{resourceRequested}"; + var response = await Service.SendRequest(HttpMethod.Get, requestUri, accessToken); + var languagePairs = await Service.DeserializeResponse>(response, property); + return languagePairs; + } + + public static async Task Translate(AccessToken accessToken, PairMapping mappedPair, Xliff sourceXliff) + { + try + { + var translationRequest = await SendTranslationRequest(accessToken, mappedPair, sourceXliff); + await WaitForTranslationCompletion(accessToken, translationRequest.RequestId); + var translation = await GetTranslationInfo(accessToken, translationRequest.RequestId, "content"); + var translatedSegment = translation.Translation.First(); + var translatedXliffSegment = Converter.ParseXliffString(translatedSegment); + return translatedXliffSegment; + } + catch (Exception ex) + { + throw ex; + } + } + + private static async Task SendTranslationRequest(AccessToken accessToken, PairMapping mappedPair, Xliff sourceXliff) + { + var requestUri = $"{accessToken.BaseUri}v4/mt/translations/async"; + var translationRequestModel = CreateTranslationRequest(mappedPair, sourceXliff); + var translationRequestModelJson = JsonConvert.SerializeObject(translationRequestModel); + var content = new StringContent(translationRequestModelJson, Encoding.UTF8, "application/json"); + var response = await Service.SendRequest(HttpMethod.Post, requestUri, accessToken, content); + var translationRequestResponse = await Service.DeserializeResponse(response); + return translationRequestResponse; + } + + private static CloudTranslationRequest CreateTranslationRequest(PairMapping mappedPair, Xliff sourceXliff) + { + const string InputFormat = "xliff"; + + var linguisticOptionsDictionary = mappedPair.LinguisticOptions?.ToDictionary(lo => lo.Id, lo => lo.SelectedValue); + var dictionaries = mappedPair.Dictionaries.Where(d => d.IsSelected).Select(d => d.DictionaryId).ToArray(); + + var translationRequestModel = new CloudTranslationRequest + { + SourceLanguageId = mappedPair.SourceCode, + TargetLanguageId = mappedPair.TargetCode, + Input = [sourceXliff.ToString()], + Model = mappedPair.SelectedModel.Model, + InputFormat = InputFormat, + Dictionaries = dictionaries, + LinguisticOptions = linguisticOptionsDictionary, + QualityEstimation = mappedPair.SelectedModel.QeSupport ? 1 : 0 + }; + + return translationRequestModel; + } + + private static async Task WaitForTranslationCompletion(AccessToken accessToken, string RequestId) + { + CloudTranslationStatus translationStatus; + bool isWaiting; + do + { + translationStatus = await GetTranslationInfo(accessToken, RequestId); + isWaiting = translationStatus.Status.Equals("init", StringComparison.OrdinalIgnoreCase) + || translationStatus.Status.Equals("translating", StringComparison.OrdinalIgnoreCase); + if (isWaiting) + { + await Task.Delay(1000); + } + } while (isWaiting); + } + + private static async Task GetTranslationInfo(AccessToken accessToken, string requestId, string endpoint = null) + { + var requestUri = $"{accessToken.BaseUri}v4/mt/translations/async/{requestId}/{endpoint}"; + var translationStatusReponse = await Service.SendRequest(HttpMethod.Get, requestUri, accessToken); + var x = await translationStatusReponse.Content.ReadAsStringAsync(); + var translationStatus = await Service.DeserializeResponse(translationStatusReponse); + return translationStatus; + } + + public static async Task CreateFeedback(AccessToken accessToken, FeedbackRequest feedbackRequest) + { + try + { + var requestUri = $"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}/feedback/translations"; + var feedbackRequestJson = JsonConvert.SerializeObject(feedbackRequest); + var content = new StringContent(feedbackRequestJson, new UTF8Encoding(), "application/json"); + var response = await Service.SendRequest(HttpMethod.Post, requestUri, accessToken, content); + response.EnsureSuccessStatusCode(); + return response.IsSuccessStatusCode; + } + catch (Exception ex) + { + ErrorHandling.ShowDialog(ex, "Feedback", ex.Message, true); + return false; + } + } + + public static async Task CreateDictionaryTerm(AccessToken accessToken, PairDictionary pairDictionary, DictionaryTerm newDictionaryTerm) + { + var requestUri = $"https://api.languageweaver.com/v4/accounts/{accessToken.AccountId}/dictionaries/{pairDictionary.DictionaryId}/terms"; + var content = JsonConvert.SerializeObject(newDictionaryTerm); + var stringContent = new StringContent(content, new UTF8Encoding(), "application/json"); + + var response = await Service.SendRequest(HttpMethod.Post, requestUri, accessToken, stringContent); + var isSuccessStatusCode = response.IsSuccessStatusCode; + if (isSuccessStatusCode) + { + return isSuccessStatusCode; + } + + var errors = await Service.DeserializeResponse(response); + var error = errors.Errors.FirstOrDefault(); + ErrorHandling.ShowDialog(null, $"Code {error?.Code}", error?.Description); + return isSuccessStatusCode; + } + + public static HttpClient GetHttpClient() + { + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Add(Constants.TraceAppKey, Constants.TraceAppValue); + httpClient.DefaultRequestHeaders.Add(Constants.TraceAppVersionKey, ApplicationInitializer.CurrentAppVersion); + return httpClient; + } + + #region Account subscription - ON HOLD + public static async Task GetAccountDetails(AccessToken accessToken) + { + var uri = new Uri($"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}/subscriptions"); + var request = new HttpRequestMessage(HttpMethod.Get, uri); + request.Headers.Add("Authorization", $"{accessToken.TokenType} {accessToken.Token}"); + var response = await GetHttpClient().SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + + var output = JsonConvert.DeserializeObject(responseContent); + return output; + } + + public static async Task> GetSubscriptionDetails(AccessToken accessToken) + { + var uri = new Uri($"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}"); + var request = new HttpRequestMessage(HttpMethod.Get, uri); + request.Headers.Add("Authorization", $"{accessToken.TokenType} {accessToken.Token}"); + var response = await GetHttpClient().SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + + var accountCategoryFeatures = JObject.Parse(responseContent)["accountSetting"]["accountCategoryFeatures"].ToObject>(); + return accountCategoryFeatures; + } + + public static async Task GetUsageReport(AccessToken accessToken, IEnumerable subscriptions) + { + var usageReport = new CloudUsageReport(); + foreach (var subscription in subscriptions.Where(sub => sub.IsActive)) + { + var startDate = DateTime.ParseExact(subscription.StartDate, "yyyy/MM/dd", null); + var endDate = DateTime.ParseExact(subscription.EndDate, "yyyy/MM/dd", null); + + for (var currentMonthStart = startDate; currentMonthStart < endDate; currentMonthStart = currentMonthStart.AddMonths(3)) + { + var currentMonthEnd = currentMonthStart.AddMonths(2); + if (currentMonthEnd > endDate) + { + currentMonthEnd = endDate; + } + + var uri = $"{accessToken.BaseUri}v4/accounts/{accessToken.AccountId}/reports/usage/translations"; + var request = new HttpRequestMessage(HttpMethod.Get, uri); + request.Headers.Add("Authorization", $"{accessToken.TokenType} {accessToken.Token}"); + + var period = new CloudSubscriptionPeriod + { + StartDate = currentMonthStart.ToString("yyyy/MM/dd", CultureInfo.InvariantCulture), + EndDate = currentMonthEnd.ToString("yyyy/MM/dd", CultureInfo.InvariantCulture) + }; + + var feedbackRequestJson = JsonConvert.SerializeObject(period); + var content = new StringContent(feedbackRequestJson, new UTF8Encoding(), "application/json"); + + var response = await Service.SendRequest(HttpMethod.Post, uri, accessToken, content); + var responseContent = await response.Content.ReadAsStringAsync(); + + var currentPeriodUsageReports = JsonConvert.DeserializeObject(responseContent); + + foreach (var currentPeriodUsageReport in currentPeriodUsageReports.Reports) + { + UpdateUsageReport(usageReport, currentPeriodUsageReport); + } + } + } + + return usageReport; + } + + private static void UpdateUsageReport(CloudUsageReport totalUsageReport, CloudUsageReport currentPeriodReport) + { + totalUsageReport.OutputWordCount += currentPeriodReport.OutputWordCount; + totalUsageReport.OutputCharCount += currentPeriodReport.OutputCharCount; + totalUsageReport.Count += currentPeriodReport.Count; + totalUsageReport.InputWordCount += currentPeriodReport.InputWordCount; + totalUsageReport.InputCharCount += currentPeriodReport.InputCharCount; + // Add more properties if needed + } + #endregion + } } \ No newline at end of file diff --git a/LanguageWeaverProvider/pluginpackage.manifest.xml b/LanguageWeaverProvider/pluginpackage.manifest.xml index a537099068..844d4b8f59 100644 --- a/LanguageWeaverProvider/pluginpackage.manifest.xml +++ b/LanguageWeaverProvider/pluginpackage.manifest.xml @@ -5,7 +5,7 @@ Language Weaver Provider Language Weaver Provider - 2.0.2.0 + 2.0.2.1