diff --git a/BuffettCodeAPIClient/ApiClientCore.cs b/BuffettCodeAPIClient/ApiClientCore.cs index 3ce9f6a..8706733 100644 --- a/BuffettCodeAPIClient/ApiClientCore.cs +++ b/BuffettCodeAPIClient/ApiClientCore.cs @@ -11,15 +11,15 @@ namespace BuffettCodeAPIClient /// BuffettCode API と Http でやり取りする Client のコアクラス /// - public class ApiClientCore + public class ApiClientCore : IDisposable, IApiClientCore { - public string ApiKey { set; get; } + private string apiKey; private readonly Uri baseUri; private static readonly long TimeoutMilliseconds = 5000; private readonly HttpClient httpClient; public ApiClientCore(string apiKey, Uri baseUri) { - this.ApiKey = apiKey; + this.apiKey = apiKey; this.baseUri = baseUri; this.httpClient = NewHttpClient(); } @@ -32,11 +32,11 @@ private HttpClient NewHttpClient() httpClient.DefaultRequestHeaders.Accept.Clear(); httpClient.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); - httpClient.DefaultRequestHeaders.Add("x-api-key", ApiKey); + httpClient.DefaultRequestHeaders.Add("x-api-key", apiKey); return httpClient; } - public static string BuildGetPath(ApiGetRequest request) + private static string BuildGetPath(ApiGetRequest request) { return $"{request.EndPoint}?{new FormUrlEncodedContent(request.Parameters).ReadAsStringAsync().Result}"; } @@ -58,10 +58,23 @@ public async Task Get(ApiGetRequest request, bool isConfigureAwait) throw new BuffettCodeApiClientException(); } } - // to do : waiting too long to read as str in csv download - return await response.Content.ReadAsStringAsync().ConfigureAwait(isConfigureAwait); + var content = response.Content.ReadAsStringAsync().Result; + return content; } } + + public void Dispose() + { + httpClient.Dispose(); + } + + public IApiClientCore SetApiKey(string apiKey) + { + this.apiKey = apiKey; + return this; + } + + public string GetApiKey() => this.apiKey; } } \ No newline at end of file diff --git a/BuffettCodeAPIClient/ApiClientCoreWithCache.cs b/BuffettCodeAPIClient/ApiClientCoreWithCache.cs index 45fe18b..1a5bc56 100644 --- a/BuffettCodeAPIClient/ApiClientCoreWithCache.cs +++ b/BuffettCodeAPIClient/ApiClientCoreWithCache.cs @@ -1,45 +1,48 @@ using System; using System.Runtime.Caching; -using System.Threading.Tasks; namespace BuffettCodeAPIClient { public class ApiClientCoreWithCache { - private readonly ApiClientCore apiClientCore; + private readonly IApiClientCore apiClientCore; private readonly ApiRequestCacheHelper cacheHelper; - private ApiClientCoreWithCache(string apiKey, Uri baseUri, MemoryCache cache) + private ApiClientCoreWithCache(IApiClientCore apiClientCore + , ApiRequestCacheHelper cacheHelper) { - this.apiClientCore = new ApiClientCore(apiKey, baseUri); - this.cacheHelper = new ApiRequestCacheHelper(cache, baseUri); + this.apiClientCore = apiClientCore; + this.cacheHelper = cacheHelper; } public static ApiClientCoreWithCache Create(string apiKey, string baseUrl, MemoryCache cache) { - return new ApiClientCoreWithCache(apiKey, new Uri(baseUrl), cache); + var baseUri = new Uri(baseUrl); + var apiClientCore = new ApiClientCore(apiKey, baseUri); + var cacheHelper = new ApiRequestCacheHelper(cache, baseUri); + return new ApiClientCoreWithCache(apiClientCore, cacheHelper); } - public static ApiClientCoreWithCache Create(string apiKey, Uri baseUrl, MemoryCache cache) + public static ApiClientCoreWithCache Create(IApiClientCore apiClientCore, ApiRequestCacheHelper cacheHelper) { - return new ApiClientCoreWithCache(apiKey, baseUrl, cache); + return new ApiClientCoreWithCache(apiClientCore, cacheHelper); } - public void UpdateApiKey(string apikey) - { - this.apiClientCore.ApiKey = apikey; - } + public void UpdateApiKey(string apiKey) => this.apiClientCore.SetApiKey(apiKey); + + public string GetApiKey() => this.apiClientCore.GetApiKey(); - public string GetApiKey() => this.apiClientCore.ApiKey; - public async Task Get(ApiGetRequest request, bool isConfigureAwait, bool useCache) + public string Get(ApiGetRequest request, bool isConfigureAwait, bool useCache) { if (useCache && cacheHelper.HasCache(request)) { return (string)cacheHelper.Get(request); } - var response = await apiClientCore.Get(request, isConfigureAwait); - cacheHelper.Set(request, response); - return response; + else + { + return apiClientCore.Get(request, isConfigureAwait).Result; + } } + } } \ No newline at end of file diff --git a/BuffettCodeAPIClient/BuffettCodeAPIClient.csproj b/BuffettCodeAPIClient/BuffettCodeAPIClient.csproj index fb4b90c..eb2ffea 100644 --- a/BuffettCodeAPIClient/BuffettCodeAPIClient.csproj +++ b/BuffettCodeAPIClient/BuffettCodeAPIClient.csproj @@ -64,6 +64,7 @@ + diff --git a/BuffettCodeAPIClient/BuffettCodeApiV2Client.cs b/BuffettCodeAPIClient/BuffettCodeApiV2Client.cs index c3694db..be68b57 100644 --- a/BuffettCodeAPIClient/BuffettCodeApiV2Client.cs +++ b/BuffettCodeAPIClient/BuffettCodeApiV2Client.cs @@ -5,7 +5,6 @@ using Newtonsoft.Json.Linq; using System; using System.Runtime.Caching; -using System.Threading.Tasks; namespace BuffettCodeAPIClient { @@ -25,31 +24,31 @@ private BuffettCodeApiV2Client() ); } - public async Task GetQuarter(string ticker, FiscalQuarterPeriod period, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) + public JObject GetQuarter(string ticker, FiscalQuarterPeriod period, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) { var request = BuffettCodeApiV2RequestCreator.CreateGetQuarterRequest(ticker, period, useOndemand); - var response = await apiClientCore.Get(request, isConfigureAwait, useCache); + var response = apiClientCore.Get(request, isConfigureAwait, useCache); return ApiGetResponseBodyParser.Parse(response); } - public async Task GetIndicator(string ticker, bool isConfigureAwait = true, bool useCache = true) + public JObject GetIndicator(string ticker, bool isConfigureAwait = true, bool useCache = true) { var request = BuffettCodeApiV2RequestCreator.CreateGetIndicatorRequest (ticker); - var response = await apiClientCore.Get(request, isConfigureAwait, useCache); + var response = apiClientCore.Get(request, isConfigureAwait, useCache); return ApiGetResponseBodyParser.Parse(response); } - public async Task GetQuarterRange(string ticker, FiscalQuarterPeriod from, FiscalQuarterPeriod to, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) + public JObject GetQuarterRange(string ticker, FiscalQuarterPeriod from, FiscalQuarterPeriod to, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) { var request = BuffettCodeApiV2RequestCreator.CreateGetQuarterRangeRequest(ticker, from, to); - var response = await apiClientCore.Get(request, isConfigureAwait, useCache); + var response = apiClientCore.Get(request, isConfigureAwait, useCache); return ApiGetResponseBodyParser.Parse(response); } - public async Task GetCompany(string ticker, bool isConfigureAwait = true, bool useCache = true) + public JObject GetCompany(string ticker, bool isConfigureAwait = true, bool useCache = true) { var request = BuffettCodeApiV2RequestCreator.CreateGetCompanyRequest(ticker); - var response = await apiClientCore.Get(request, isConfigureAwait, useCache); + var response = apiClientCore.Get(request, isConfigureAwait, useCache); return ApiGetResponseBodyParser.Parse(response); } @@ -64,7 +63,7 @@ public static BuffettCodeApiV2Client GetInstance(string apiKey) } - public Task Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) + public JObject Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) { switch (dataType) { @@ -79,7 +78,7 @@ public Task Get(DataTypeConfig dataType, string ticker, IPeriod period, } } - public Task GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) + public JObject GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) { switch (dataType) { diff --git a/BuffettCodeAPIClient/BuffettCodeApiV3Client.cs b/BuffettCodeAPIClient/BuffettCodeApiV3Client.cs index afb7486..f977c77 100644 --- a/BuffettCodeAPIClient/BuffettCodeApiV3Client.cs +++ b/BuffettCodeAPIClient/BuffettCodeApiV3Client.cs @@ -5,7 +5,6 @@ using BuffettCodeCommon.Validator; using Newtonsoft.Json.Linq; using System.Runtime.Caching; -using System.Threading.Tasks; namespace BuffettCodeAPIClient @@ -27,11 +26,11 @@ private BuffettCodeApiV3Client() } - public async Task GetDaily(string ticker, DayPeriod day, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) + public JObject GetDaily(string ticker, DayPeriod day, bool useOndemand, bool isConfigureAwait = true, bool useCache = true) { var request = BuffettCodeApiV3RequestCreator.CreateGetDailyRequest(ticker, day, useOndemand); JpTickerValidator.Validate(ticker); - var response = await apiClientCore.Get(request, isConfigureAwait, useCache); + var response = apiClientCore.Get(request, isConfigureAwait, useCache); return ApiGetResponseBodyParser.Parse(response); } @@ -45,7 +44,7 @@ public static BuffettCodeApiV3Client GetInstance(string apiKey) return instance; } - public Task Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait, bool useCache) + public JObject Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait, bool useCache) { switch (dataType) { @@ -56,7 +55,7 @@ public Task Get(DataTypeConfig dataType, string ticker, IPeriod period, } } - public Task GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait, bool useCache) + public JObject GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait, bool useCache) { switch (dataType) { diff --git a/BuffettCodeAPIClient/IApiClientCore.cs b/BuffettCodeAPIClient/IApiClientCore.cs new file mode 100644 index 0000000..0537c32 --- /dev/null +++ b/BuffettCodeAPIClient/IApiClientCore.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace BuffettCodeAPIClient +{ + public interface IApiClientCore + { + Task Get(ApiGetRequest request, bool isConfigureAwait); + + IApiClientCore SetApiKey(string apiKey); + string GetApiKey(); + + } +} \ No newline at end of file diff --git a/BuffettCodeAPIClient/IBuffettCodeApiClient.cs b/BuffettCodeAPIClient/IBuffettCodeApiClient.cs index 81a8f1a..a0e3e67 100644 --- a/BuffettCodeAPIClient/IBuffettCodeApiClient.cs +++ b/BuffettCodeAPIClient/IBuffettCodeApiClient.cs @@ -1,15 +1,14 @@ using BuffettCodeCommon.Config; using BuffettCodeCommon.Period; using Newtonsoft.Json.Linq; -using System.Threading.Tasks; namespace BuffettCodeAPIClient { public interface IBuffettCodeApiClient { - Task Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait, bool useCache); - Task GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait, bool useCache); + JObject Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait, bool useCache); + JObject GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait, bool useCache); string GetApiKey(); void UpdateApiKey(string apiKey); diff --git a/BuffettCodeAPIClientTests/ApiClientCoreTests.cs b/BuffettCodeAPIClientTests/ApiClientCoreTests.cs index d915df8..976bf71 100644 --- a/BuffettCodeAPIClientTests/ApiClientCoreTests.cs +++ b/BuffettCodeAPIClientTests/ApiClientCoreTests.cs @@ -32,5 +32,17 @@ var clentCore Assert.AreEqual(1, xApiKeyHeaders.Count); Assert.AreEqual(apiKey, xApiKeyHeaders[0]); } + + [TestMethod()] + public void SetGetApiKeyTest() + { + var initKey = "init_api_key"; + var newKey = "new_api_key"; + var baseUri = new Uri(@"https://example.com"); + var clientCore + = new ApiClientCore(initKey, baseUri); + Assert.AreEqual(initKey, clientCore.GetApiKey()); + Assert.AreEqual(newKey, clientCore.SetApiKey(newKey).GetApiKey()); + } } } \ No newline at end of file diff --git a/BuffettCodeAPIClientTests/ApiClientCoreWithCacheTests.cs b/BuffettCodeAPIClientTests/ApiClientCoreWithCacheTests.cs index 5462834..a9ee4ab 100644 --- a/BuffettCodeAPIClientTests/ApiClientCoreWithCacheTests.cs +++ b/BuffettCodeAPIClientTests/ApiClientCoreWithCacheTests.cs @@ -1,17 +1,72 @@ +using BuffettCodeCommon.Exception; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; using System.Runtime.Caching; +using System.Threading.Tasks; namespace BuffettCodeAPIClient.Tests { + class MockApiclientCore : IApiClientCore + { + private string apiKey; + public MockApiclientCore(string apiKey) + { + this.apiKey = apiKey; + } + + + public Task Get(ApiGetRequest request, bool isConfigureAwait) + { + return Task.FromResult(request.ToString()); + } + + public string GetApiKey() => this.apiKey; + public IApiClientCore SetApiKey(string apiKey) + { + this.apiKey = apiKey; + return this; + } + } + + class ErrorMockApiClientCore : IApiClientCore + { + private string apiKey; + public ErrorMockApiClientCore(string apiKey) + { + this.apiKey = apiKey; + } + + + public Task Get(ApiGetRequest request, bool isConfigureAwait) + { + throw new BuffettCodeApiClientException(); + } + + public string GetApiKey() => this.apiKey; + public IApiClientCore SetApiKey(string apikey) + { + apiKey = apikey; + return this; + } + } + [TestClass()] public class ApiClientCoreWithCacheTests { + private static ApiRequestCacheHelper CreateCheHelperForTest() + { + var cacheKey = new Random().Next().GetHashCode().ToString(); + return new ApiRequestCacheHelper(new MemoryCache(cacheKey), new Uri("http://example.com")); + } + [TestMethod()] public void GetAndUpdateApiKeyTest() { var initApiKey = @"init-key"; var newApiKey = @"new-key"; - var client = ApiClientCoreWithCache.Create(initApiKey, @"http://example.com", new MemoryCache(@"GetAndUpdateApiKeyTest")); + var mockApiClientCore = new MockApiclientCore(initApiKey); + var client = ApiClientCoreWithCache.Create(mockApiClientCore, CreateCheHelperForTest()); // test init value Assert.AreEqual(initApiKey, client.GetApiKey()); @@ -19,5 +74,28 @@ public void GetAndUpdateApiKeyTest() client.UpdateApiKey(newApiKey); Assert.AreEqual(newApiKey, client.GetApiKey()); } + + [TestMethod()] + public void GetTest() + { + var mockApiClientCore = new MockApiclientCore("dummy"); + var client = ApiClientCoreWithCache.Create(mockApiClientCore, CreateCheHelperForTest()); + ApiGetRequest request = new ApiGetRequest("dummy endpoint", new Dictionary()); + Assert.AreEqual(request.ToString(), client.Get(request, false, true)); + // use cache + Assert.AreEqual(request.ToString(), client.Get(request, false, true)); + + // don't use cache + Assert.AreEqual(request.ToString(), client.Get(request, false, false)); + } + + [TestMethod()] + public void GetExceptionTest() + { + var mockApiClientCore = new ErrorMockApiClientCore("dummy"); + var client = ApiClientCoreWithCache.Create(mockApiClientCore, CreateCheHelperForTest()); + ApiGetRequest request = new ApiGetRequest("dummy endpoint", new Dictionary()); + Assert.ThrowsException(() => client.Get(request, false, false)); + } } } \ No newline at end of file diff --git a/BuffettCodeAPIClientTests/BuffettCodeApiV2ClientTests.cs b/BuffettCodeAPIClientTests/BuffettCodeApiV2ClientTests.cs index 04bcb6c..23a6433 100644 --- a/BuffettCodeAPIClientTests/BuffettCodeApiV2ClientTests.cs +++ b/BuffettCodeAPIClientTests/BuffettCodeApiV2ClientTests.cs @@ -1,5 +1,4 @@ using BuffettCodeCommon.Config; -using BuffettCodeCommon.Exception; using BuffettCodeCommon.Period; namespace BuffettCodeAPIClient.Tests @@ -26,20 +25,14 @@ public void GetQuarterTest() { var period = FiscalQuarterPeriod.Create(2019, 4); // test api key can get ticker=xx01 - Assert.IsNotNull(client.GetQuarter("6501", period, false, true, false).Result); - - // test api key can get ticker=xx02 - Assert.ThrowsExceptionAsync(() => client.GetQuarter("6502", period, false, true, false)); + Assert.IsNotNull(client.GetQuarter("6501", period, false, true, false)); } [TestMethod()] public void GetIndicatorTest() { // test api key can get ticker=xx01 - Assert.IsNotNull(client.GetIndicator("6501", true).Result); - - // test api key can get ticker=xx02 - Assert.ThrowsExceptionAsync(() => client.GetIndicator("6502", true, false)); + Assert.IsNotNull(client.GetIndicator("6501", true)); } [TestMethod()] @@ -49,20 +42,14 @@ public void GetQuarterRangeTest() var to = FiscalQuarterPeriod.Create(2019, 4); // test api key can get ticker=xx01 - Assert.IsNotNull(client.GetQuarterRange("6501", from, to, true, false).Result); - - // test api key can get ticker=xx02 - Assert.ThrowsExceptionAsync(() => client.GetQuarterRange("6502", from, to, true, false)); + Assert.IsNotNull(client.GetQuarterRange("6501", from, to, false, false)); } [TestMethod()] public void GetCompanyTest() { // test api key can get ticker=xx01 - Assert.IsNotNull(client.GetCompany("6501", true, true).Result); - - // test api key can get ticker=xx02 - Assert.ThrowsExceptionAsync(() => client.GetCompany("6502", true, false)); + Assert.IsNotNull(client.GetCompany("6501", true, false)); } diff --git a/BuffettCodeAPIClientTests/BuffettCodeApiV3ClientTests.cs b/BuffettCodeAPIClientTests/BuffettCodeApiV3ClientTests.cs index 56e568e..d186d8d 100644 --- a/BuffettCodeAPIClientTests/BuffettCodeApiV3ClientTests.cs +++ b/BuffettCodeAPIClientTests/BuffettCodeApiV3ClientTests.cs @@ -1,5 +1,4 @@ using BuffettCodeCommon.Config; -using BuffettCodeCommon.Exception; using BuffettCodeCommon.Period; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -25,11 +24,7 @@ public void GetDailyTest() { // test api key can get 01 data var day = DayPeriod.Create(2021, 2, 1); - Assert.IsNotNull(client.GetDaily("6501", day, false, true, false).Result); - - // test api key can get not 01 - Assert.ThrowsExceptionAsync - (() => client.GetDaily("6502", day, true, true, false)); + Assert.IsNotNull(client.GetDaily("6501", day, false, true, false)); } } } \ No newline at end of file diff --git a/BuffettCodeAddinRibbon/CsvDownload/ApiResourceGetter.cs b/BuffettCodeAddinRibbon/CsvDownload/ApiResourceGetter.cs index b2948c1..2dab45d 100644 --- a/BuffettCodeAddinRibbon/CsvDownload/ApiResourceGetter.cs +++ b/BuffettCodeAddinRibbon/CsvDownload/ApiResourceGetter.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; -using System.Linq; +using BuffettCodeAddinRibbon.Settings; using BuffettCodeCommon; using BuffettCodeCommon.Config; using BuffettCodeCommon.Period; using BuffettCodeIO; using BuffettCodeIO.Property; -using BuffettCodeAddinRibbon.Settings; +using System.Collections.Generic; +using System.Linq; namespace BuffettCodeAddinRibbon.CsvDownload { @@ -20,7 +20,7 @@ private ApiResourceGetter(BuffettCodeApiTaskProcessor processor) public static ApiResourceGetter Create(Configuration config) { - var processor = new BuffettCodeApiTaskProcessor(config.ApiVersion, config.ApiKey, config.MaxDegreeOfParallelism, config.IsOndemandEndpointEnabled); + var processor = new BuffettCodeApiTaskProcessor(config.ApiVersion, config.ApiKey, config.IsOndemandEndpointEnabled); return new ApiResourceGetter(processor); } @@ -31,9 +31,12 @@ public static ApiResourceGetter Create() public IEnumerable GetQuarters(CsvDownloadParameters parameters) { + + // set isConfigureAwait for performance + // https://devblogs.microsoft.com/dotnet/configureawait-faq/#why-would-i-want-to-use-configureawaitfalse var quarters = PeriodRange.Slice(parameters.Range, 12) .SelectMany - (r => processor.GetApiResources(DataTypeConfig.Quarter, parameters.Ticker, r.From, r.To, true, true) + (r => processor.GetApiResources(DataTypeConfig.Quarter, parameters.Ticker, r.From, r.To, false, true) ).Cast(); return quarters.Distinct().OrderBy(q => q.Period); } diff --git a/BuffettCodeAddinRibbon/CsvDownloadForm.cs b/BuffettCodeAddinRibbon/CsvDownloadForm.cs index f33db46..9b6bf4a 100644 --- a/BuffettCodeAddinRibbon/CsvDownloadForm.cs +++ b/BuffettCodeAddinRibbon/CsvDownloadForm.cs @@ -20,6 +20,7 @@ public partial class CsvDownloadForm : Form private static readonly int upperLimitYear = DateTime.Today.Year; private readonly TabularWriterBuilder quarterTabularWriterBuilder = new TabularWriterBuilder(); + private readonly ApiResourceGetter apiResourceGetter = ApiResourceGetter.Create(); public CsvDownloadForm() { @@ -69,7 +70,8 @@ private void Execute() try { var parameters = CreateParametersFromFormValues(); - var quarters = ApiResourceGetter.Create().GetQuarters(parameters).ToList(); + var resources = apiResourceGetter.GetQuarters(parameters); + var quarters = resources.ToList(); if (quarters.Count == 0) { MessageBox.Show("条件に当てはまる財務データがありませんでした。", "CSV出力", MessageBoxButtons.OK); @@ -81,6 +83,7 @@ private void Execute() writer.Write(tabular); MessageBox.Show("財務データの取得が完了しました", "CSV出力", MessageBoxButtons.OK); } + DialogResult = DialogResult.OK; } catch (TestAPIConstraintException) { @@ -110,7 +113,6 @@ private void Execute() { MessageBox.Show("データの取得中にエラーが発生しました。", "CSV出力", MessageBoxButtons.OK); } - DialogResult = DialogResult.OK; Close(); } diff --git a/BuffettCodeAddinRibbon/Ribbon.cs b/BuffettCodeAddinRibbon/Ribbon.cs index dc2a878..0612ba9 100644 --- a/BuffettCodeAddinRibbon/Ribbon.cs +++ b/BuffettCodeAddinRibbon/Ribbon.cs @@ -24,7 +24,7 @@ private void SettingButton_Click(object sender, RibbonControlEventArgs e) { try { - var newSettings = AddinSettings.Create(form.GetAPIKey(), form.IsOndemandEndpointEnabled(), form.GetMaxDegreeOfParallelism(), form.IsDebugMode()); + var newSettings = AddinSettings.Create(form.GetAPIKey(), form.IsOndemandEndpointEnabled(), form.IsDebugMode()); newSettings.SaveToConfiguration(config); } catch (AddinConfigurationException) diff --git a/BuffettCodeAddinRibbon/SettingForm.Designer.cs b/BuffettCodeAddinRibbon/SettingForm.Designer.cs index d858c74..5aa38c6 100644 --- a/BuffettCodeAddinRibbon/SettingForm.Designer.cs +++ b/BuffettCodeAddinRibbon/SettingForm.Designer.cs @@ -39,9 +39,6 @@ private void InitializeComponent() this.labelAPIKey = new System.Windows.Forms.Label(); this.textAPIKey = new System.Windows.Forms.TextBox(); this.tabDeveloper = new System.Windows.Forms.TabPage(); - this.textParallelism = new System.Windows.Forms.TextBox(); - this.labelMaxDegreeOfParallelism = new System.Windows.Forms.Label(); - this.checkParallelism = new System.Windows.Forms.CheckBox(); this.checkDebugMode = new System.Windows.Forms.CheckBox(); this.ondemandUsageEntryLink = new System.Windows.Forms.LinkLabel(); this.tabConrtoll.SuspendLayout(); @@ -107,7 +104,7 @@ private void InitializeComponent() this.apiSpecialNotesLink.TabIndex = 7; this.apiSpecialNotesLink.TabStop = true; this.apiSpecialNotesLink.Text = "バフェットコード WEB API機能に関する特記事項"; - this.apiSpecialNotesLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.apiSpecialNotes_LinkClicked); + this.apiSpecialNotesLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.ApiSpecialNotes_LinkClicked); // // ondemandModeDesc // @@ -121,7 +118,7 @@ private void InitializeComponent() this.ondemandModeDesc.Size = new System.Drawing.Size(507, 235); this.ondemandModeDesc.TabIndex = 6; this.ondemandModeDesc.Text = resources.GetString("ondemandModeDesc.Text"); - this.ondemandModeDesc.TextChanged += new System.EventHandler(this.ondemandModeDescDesc_TextChanged); + this.ondemandModeDesc.TextChanged += new System.EventHandler(this.OndemandModeDescDesc_TextChanged); // // checkIsOndemandEndpointEnabled // @@ -148,13 +145,10 @@ private void InitializeComponent() this.textAPIKey.Name = "textAPIKey"; this.textAPIKey.Size = new System.Drawing.Size(518, 25); this.textAPIKey.TabIndex = 2; - this.textAPIKey.TextChanged += new System.EventHandler(this.textAPIKey_TextChanged); + this.textAPIKey.TextChanged += new System.EventHandler(this.TextAPIKey_TextChanged); // // tabDeveloper // - this.tabDeveloper.Controls.Add(this.textParallelism); - this.tabDeveloper.Controls.Add(this.labelMaxDegreeOfParallelism); - this.tabDeveloper.Controls.Add(this.checkParallelism); this.tabDeveloper.Controls.Add(this.checkDebugMode); this.tabDeveloper.Location = new System.Drawing.Point(4, 28); this.tabDeveloper.Name = "tabDeveloper"; @@ -164,43 +158,16 @@ private void InitializeComponent() this.tabDeveloper.Text = "開発者向けオプション"; this.tabDeveloper.UseVisualStyleBackColor = true; // - // textParallelism - // - this.textParallelism.Location = new System.Drawing.Point(276, 68); - this.textParallelism.Name = "textParallelism"; - this.textParallelism.Size = new System.Drawing.Size(100, 25); - this.textParallelism.TabIndex = 9; - this.textParallelism.Text = "1"; - // - // labelMaxDegreeOfParallelism - // - this.labelMaxDegreeOfParallelism.AutoSize = true; - this.labelMaxDegreeOfParallelism.Location = new System.Drawing.Point(110, 71); - this.labelMaxDegreeOfParallelism.Name = "labelMaxDegreeOfParallelism"; - this.labelMaxDegreeOfParallelism.Size = new System.Drawing.Size(134, 18); - this.labelMaxDegreeOfParallelism.TabIndex = 8; - this.labelMaxDegreeOfParallelism.Text = "最大同時実行数"; - // - // checkParallelism - // - this.checkParallelism.AutoSize = true; - this.checkParallelism.Location = new System.Drawing.Point(30, 39); - this.checkParallelism.Name = "checkParallelism"; - this.checkParallelism.Size = new System.Drawing.Size(234, 22); - this.checkParallelism.TabIndex = 7; - this.checkParallelism.Text = "APIの実行ペースを制限する"; - this.checkParallelism.UseVisualStyleBackColor = true; - this.checkParallelism.CheckedChanged += new System.EventHandler(this.CheckParallelism_CheckedChanged); - // // checkDebugMode // this.checkDebugMode.AutoSize = true; - this.checkDebugMode.Location = new System.Drawing.Point(30, 111); + this.checkDebugMode.Location = new System.Drawing.Point(15, 15); this.checkDebugMode.Name = "checkDebugMode"; this.checkDebugMode.Size = new System.Drawing.Size(223, 22); this.checkDebugMode.TabIndex = 6; this.checkDebugMode.Text = "デバッグモードを有効にする"; this.checkDebugMode.UseVisualStyleBackColor = true; + this.checkDebugMode.CheckedChanged += new System.EventHandler(this.CheckDebugMode_CheckedChanged); // // ondemandUsageEntryLink // @@ -213,7 +180,7 @@ private void InitializeComponent() this.ondemandUsageEntryLink.TabIndex = 7; this.ondemandUsageEntryLink.TabStop = true; this.ondemandUsageEntryLink.Text = "Web API従量課金エンドポイントのご利用にあたって"; - this.ondemandUsageEntryLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.ondemandUsageEntryLink_LinkClicked); + this.ondemandUsageEntryLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OndemandUsageEntryLink_LinkClicked); // // SettingForm // @@ -248,9 +215,6 @@ private void InitializeComponent() private System.Windows.Forms.Label labelAPIKey; private System.Windows.Forms.TextBox textAPIKey; private System.Windows.Forms.TabPage tabDeveloper; - private System.Windows.Forms.TextBox textParallelism; - private System.Windows.Forms.Label labelMaxDegreeOfParallelism; - private System.Windows.Forms.CheckBox checkParallelism; private System.Windows.Forms.CheckBox checkDebugMode; private System.Windows.Forms.CheckBox checkIsOndemandEndpointEnabled; private System.Windows.Forms.TextBox ondemandModeDesc; diff --git a/BuffettCodeAddinRibbon/SettingForm.cs b/BuffettCodeAddinRibbon/SettingForm.cs index 90fd8b8..2568827 100644 --- a/BuffettCodeAddinRibbon/SettingForm.cs +++ b/BuffettCodeAddinRibbon/SettingForm.cs @@ -13,19 +13,6 @@ public SettingForm(AddinSettings setting) textAPIKey.Text = setting.ApiKey; checkDebugMode.Checked = setting.DebugMode; checkIsOndemandEndpointEnabled.Checked = setting.IsOndemandEndpointEnabled; - var maxDegreeOfParallelism = setting.MaxDegreeOfParallelism; - if (maxDegreeOfParallelism == 0) - { - checkParallelism.Checked = false; - textParallelism.Text = ""; - textParallelism.Enabled = false; - } - else - { - checkParallelism.Checked = true; - textParallelism.Text = maxDegreeOfParallelism.ToString(); - textParallelism.Enabled = true; - } } private void SettingForm_Load(object sender, EventArgs e) @@ -38,23 +25,6 @@ public string GetAPIKey() return textAPIKey.Text; } - public uint GetMaxDegreeOfParallelism() - { - if (!checkParallelism.Checked) - { - return 0; - } - else if (string.IsNullOrWhiteSpace(textParallelism.Text)) - { - return 0; - } - else if (uint.TryParse(textParallelism.Text, out uint i)) - { - return i; - } - return 0; - } - public bool IsDebugMode() { return checkDebugMode.Checked; @@ -73,29 +43,22 @@ private void ButtonCancel_Click(object sender, EventArgs e) DialogResult = DialogResult.Cancel; Close(); } - - private void CheckParallelism_CheckedChanged(object sender, EventArgs e) - { - textParallelism.Enabled = checkParallelism.Checked; - } - - private void TabAPI_Click(object sender, EventArgs e) { } - private void textAPIKey_TextChanged(object sender, EventArgs e) + private void TextAPIKey_TextChanged(object sender, EventArgs e) { } - private void ondemandModeDescDesc_TextChanged(object sender, EventArgs e) + private void OndemandModeDescDesc_TextChanged(object sender, EventArgs e) { } - private void apiSpecialNotes_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + private void ApiSpecialNotes_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { // mark as visited apiSpecialNotesLink.LinkVisited = true; @@ -104,12 +67,17 @@ private void apiSpecialNotes_LinkClicked(object sender, LinkLabelLinkClickedEven } - private void ondemandUsageEntryLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + private void OndemandUsageEntryLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { // mark as visited ondemandUsageEntryLink.LinkVisited = true; // open link using a default browser System.Diagnostics.Process.Start(ApiRelatedUrlConfig.ONDEMAND_API_USAGE_ENTRY); } + + private void CheckDebugMode_CheckedChanged(object sender, EventArgs e) + { + + } } } diff --git a/BuffettCodeAddinRibbon/Settings/AddinSettings.cs b/BuffettCodeAddinRibbon/Settings/AddinSettings.cs index 96904e4..73c0a43 100644 --- a/BuffettCodeAddinRibbon/Settings/AddinSettings.cs +++ b/BuffettCodeAddinRibbon/Settings/AddinSettings.cs @@ -7,38 +7,34 @@ public class AddinSettings { private readonly string apiKey; private readonly bool enabledOndemandEndpoint; - private readonly uint maxDegreeOfParallelism; private readonly bool debugMode; public string ApiKey => apiKey; public bool IsOndemandEndpointEnabled => enabledOndemandEndpoint; - public uint MaxDegreeOfParallelism => maxDegreeOfParallelism; public bool DebugMode => debugMode; - private AddinSettings(string apiKey, bool useOndemandEndpoint, uint maxDegreeOfParallelism, bool debugMode) + private AddinSettings(string apiKey, bool useOndemandEndpoint, bool debugMode) { this.apiKey = apiKey; this.enabledOndemandEndpoint = useOndemandEndpoint; - this.maxDegreeOfParallelism = maxDegreeOfParallelism; this.debugMode = debugMode; } - public static AddinSettings Create(string apiKey, bool useOndemandEndpoint, uint maxDegreeOfParallelism, bool debugMode) + public static AddinSettings Create(string apiKey, bool useOndemandEndpoint, bool debugMode) { ApiKeyValidator.Validate(apiKey); - return new AddinSettings(apiKey, useOndemandEndpoint, maxDegreeOfParallelism, debugMode); + return new AddinSettings(apiKey, useOndemandEndpoint, debugMode); } public static AddinSettings Create(Configuration config) { - return new AddinSettings(config.ApiKey, config.IsOndemandEndpointEnabled, config.MaxDegreeOfParallelism, config.DebugMode); + return new AddinSettings(config.ApiKey, config.IsOndemandEndpointEnabled, config.DebugMode); } public void SaveToConfiguration(Configuration config) { config.ApiKey = apiKey; config.IsOndemandEndpointEnabled = enabledOndemandEndpoint; - config.MaxDegreeOfParallelism = maxDegreeOfParallelism; config.DebugMode = debugMode; } diff --git a/BuffettCodeAddinRibbonTests/CsvDownload/ApiResourceGetterTests.cs b/BuffettCodeAddinRibbonTests/CsvDownload/ApiResourceGetterTests.cs index 33c5f3d..93e739a 100644 --- a/BuffettCodeAddinRibbonTests/CsvDownload/ApiResourceGetterTests.cs +++ b/BuffettCodeAddinRibbonTests/CsvDownload/ApiResourceGetterTests.cs @@ -1,14 +1,9 @@ -using BuffettCodeAddinRibbon.CsvDownload; using BuffettCodeAddinRibbon.Settings; using BuffettCodeCommon; using BuffettCodeCommon.Config; using BuffettCodeCommon.Period; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace BuffettCodeAddinRibbon.CsvDownload.Tests diff --git a/BuffettCodeCommon/Config/BuffettCodeRegistryConfig.cs b/BuffettCodeCommon/Config/BuffettCodeRegistryConfig.cs index 50302ba..cebcf01 100644 --- a/BuffettCodeCommon/Config/BuffettCodeRegistryConfig.cs +++ b/BuffettCodeCommon/Config/BuffettCodeRegistryConfig.cs @@ -11,7 +11,7 @@ public static string SubKeyBuffettCodeExcelAddinTest = @"Software\BuffettCode\ExcelAddinTest"; public static readonly string NameApiKey = "ApiKey"; - public static readonly string NameMaxDegreeOfParallelism = "MaxDegreeOfParallelism"; + public static readonly string NameIsOndemandEndpointEnabled = "IsOndemandEndpointEnabled"; public static readonly string NameClearCache = "ClearCache"; @@ -27,7 +27,6 @@ public static string SubKeyBuffettCodeExcelAddinTest public static readonly ISet SupportedValueNames = new HashSet { NameApiKey, - NameMaxDegreeOfParallelism, NameIsOndemandEndpointEnabled, NameClearCache, NameDebugMode, diff --git a/BuffettCodeCommon/Configuration.cs b/BuffettCodeCommon/Configuration.cs index 8c79456..881add4 100644 --- a/BuffettCodeCommon/Configuration.cs +++ b/BuffettCodeCommon/Configuration.cs @@ -38,12 +38,6 @@ public static Configuration GetInstance(bool isDev = false) /// private static readonly string ApiKeyDefault = BuffettCodeApiKeyConfig.TestApiKey; - /// - /// 最大同時実行数のデフォルト値 - /// - private static readonly uint MaxDegreeOfParallelismDefault = 8; - - /// /// デフォルトでは Ondemand Endpoint は利用不可 /// @@ -77,15 +71,6 @@ public string ApiKey } - /// - /// APIコールの最大同時実行数 - /// - public uint MaxDegreeOfParallelism - { - get => uint.Parse(registryAccessor.GetRegistryValue(BuffettCodeRegistryConfig.NameMaxDegreeOfParallelism, MaxDegreeOfParallelismDefault).ToString()); - set => registryAccessor.SaveRegistryValue(BuffettCodeRegistryConfig.NameMaxDegreeOfParallelism, value); - } - // Registryには"True" "False" の文字列で書き込まれる private bool IsTrue(string name, bool defaultValue) => registryAccessor.GetRegistryValue(name, defaultValue).ToString().Equals(true.ToString()); @@ -98,7 +83,6 @@ public bool IsOndemandEndpointEnabled set => registryAccessor.SaveRegistryValue(BuffettCodeRegistryConfig.NameIsOndemandEndpointEnabled, value); } - /// /// デバッグモード /// diff --git a/BuffettCodeCommonTests/ConfigurationTests.cs b/BuffettCodeCommonTests/ConfigurationTests.cs index 9b99730..36f8699 100644 --- a/BuffettCodeCommonTests/ConfigurationTests.cs +++ b/BuffettCodeCommonTests/ConfigurationTests.cs @@ -60,18 +60,6 @@ public void ApiKeyTest() } - [TestMethod()] - public void MaxDegreeOfParallelismTest() - { - // check default value - Assert.AreEqual((uint)8, configForTest.MaxDegreeOfParallelism); - - // set and get - var maxDegreeOfParallelism = (uint)randomizer.Next(100); - configForTest.MaxDegreeOfParallelism = maxDegreeOfParallelism; - Assert.AreEqual(maxDegreeOfParallelism, configForTest.MaxDegreeOfParallelism); - } - [TestMethod()] public void UseOndemandEndpointTest() { diff --git a/BuffettCodeExcelFunctions/ApiResourceFetcher.cs b/BuffettCodeExcelFunctions/ApiResourceFetcher.cs index f1f3182..3193ebc 100644 --- a/BuffettCodeExcelFunctions/ApiResourceFetcher.cs +++ b/BuffettCodeExcelFunctions/ApiResourceFetcher.cs @@ -11,7 +11,7 @@ public class ApiResourceFetcher { private static readonly Configuration config = Configuration.GetInstance(); private static readonly IDataTypeResolver resolver = V2DataTypeResolverFactory.Create(); - private static readonly BuffettCodeApiTaskProcessor processor = new BuffettCodeApiTaskProcessor(config.ApiVersion, config.ApiKey, config.MaxDegreeOfParallelism, config.IsOndemandEndpointEnabled + private static readonly BuffettCodeApiTaskProcessor processor = new BuffettCodeApiTaskProcessor(config.ApiVersion, config.ApiKey, config.IsOndemandEndpointEnabled ); private static bool IsQuarterCall(string parameter1, string parameter2) @@ -37,7 +37,7 @@ public static IApiResource FetchForLegacy throw new NotSupportedDataTypeException(); } } - public static IApiResource Fetch(DataTypeConfig dataType, string ticker, IPeriod period, bool isConfigureAwait = true, bool useCache = true) => processor.UpdateIfNeeded(config.ApiKey, config.MaxDegreeOfParallelism, config.IsOndemandEndpointEnabled).GetApiResource(dataType, ticker, period, isConfigureAwait, useCache); + public static IApiResource Fetch(DataTypeConfig dataType, string ticker, IPeriod period, bool isConfigureAwait = true, bool useCache = true) => processor.UpdateIfNeeded(config.ApiKey, config.IsOndemandEndpointEnabled).GetApiResource(dataType, ticker, period, isConfigureAwait, useCache); } } \ No newline at end of file diff --git a/BuffettCodeIO/ApiTaskHelper.cs b/BuffettCodeIO/ApiTaskHelper.cs index 34089e6..9099b85 100644 --- a/BuffettCodeIO/ApiTaskHelper.cs +++ b/BuffettCodeIO/ApiTaskHelper.cs @@ -14,9 +14,10 @@ public ApiTaskHelper(PeriodSupportedTierResolver tierResolver) this.tierResolver = tierResolver; } - public SupportedTier FindAvailableTier(DataTypeConfig dataType, string ticker, IPeriod period, bool IsOndemandEndpointEnabled) + public SupportedTier FindAvailableTier(DataTypeConfig dataType, string ticker, IPeriod period, bool IsOndemandEndpointEnabled, bool isConfigureAwait, bool useCache) { - var tier = tierResolver.Resolve(dataType, ticker, period); + var tier = tierResolver.Resolve(dataType, ticker, period, + isConfigureAwait, useCache); switch (tier) { case SupportedTier.None: @@ -36,8 +37,7 @@ public SupportedTier FindAvailableTier(DataTypeConfig dataType, string ticker, I throw new NotSupportedTierException($"unknown tier:{tier} for {dataType}, {ticker},{period}"); } } - - public bool ShouldUseOndemandEndpoint(DataTypeConfig dataType, string ticker, IPeriod period, bool IsOndemandEndpointEnabled) + public bool ShouldUseOndemandEndpoint(DataTypeConfig dataType, string ticker, IPeriod period, bool IsOndemandEndpointEnabled, bool isConfigureAwait, bool useCache) { if (dataType == DataTypeConfig.Indicator) { @@ -45,14 +45,14 @@ public bool ShouldUseOndemandEndpoint(DataTypeConfig dataType, string ticker, IP } else { - return FindAvailableTier(dataType, ticker, period, IsOndemandEndpointEnabled).Equals(SupportedTier.OndemandTier); + return FindAvailableTier(dataType, ticker, period, IsOndemandEndpointEnabled, isConfigureAwait, useCache).Equals(SupportedTier.OndemandTier); } } - public IComparablePeriod FindEndOfOndemandPeriod(DataTypeConfig dataType, string ticker, PeriodRange periodRange, bool IsOndemandEndpointEnabled) + public IComparablePeriod FindEndOfOndemandPeriod(DataTypeConfig dataType, string ticker, PeriodRange periodRange, bool IsOndemandEndpointEnabled, bool isConfigureAwait, bool useCache) { - var fromTier = FindAvailableTier(dataType, ticker, periodRange.From, IsOndemandEndpointEnabled); - var toTier = FindAvailableTier(dataType, ticker, periodRange.To, IsOndemandEndpointEnabled); + var fromTier = FindAvailableTier(dataType, ticker, periodRange.From, IsOndemandEndpointEnabled, isConfigureAwait, useCache); + var toTier = FindAvailableTier(dataType, ticker, periodRange.To, IsOndemandEndpointEnabled, isConfigureAwait, useCache); if (fromTier.Equals(SupportedTier.FixedTier)) { @@ -69,7 +69,7 @@ public IComparablePeriod FindEndOfOndemandPeriod(DataTypeConfig dataType, string var period = periodRange.From.Next(); while (period.CompareTo(periodRange.To) < 0) { - var tier = FindAvailableTier(dataType, ticker, period.Next(), IsOndemandEndpointEnabled); + var tier = FindAvailableTier(dataType, ticker, period.Next(), IsOndemandEndpointEnabled, isConfigureAwait, useCache); if (tier.Equals(SupportedTier.FixedTier)) { break; diff --git a/BuffettCodeIO/BuffettCodeApiTaskProcessor.cs b/BuffettCodeIO/BuffettCodeApiTaskProcessor.cs index bae2b26..11c0ebd 100644 --- a/BuffettCodeIO/BuffettCodeApiTaskProcessor.cs +++ b/BuffettCodeIO/BuffettCodeApiTaskProcessor.cs @@ -2,10 +2,8 @@ using BuffettCodeCommon.Config; using BuffettCodeCommon.Period; using BuffettCodeIO.Parser; -using BuffettCodeIO.Processor; using BuffettCodeIO.Property; using BuffettCodeIO.Resolver; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; @@ -16,22 +14,20 @@ public class BuffettCodeApiTaskProcessor { private readonly IBuffettCodeApiClient client; private readonly IApiResponseParser parser; - private readonly ITaskProcessor processor; private readonly ApiTaskHelper taskHelper; private bool isOndemandEndpointEnabled; private static readonly object updateLock = new object(); - public BuffettCodeApiTaskProcessor(BuffettCodeApiVersion version, string apiKey, uint maxDegreeOfParallelism, bool useOndemandEndpoint) + public BuffettCodeApiTaskProcessor(BuffettCodeApiVersion version, string apiKey, bool useOndemandEndpoint) { client = ApiClientInstanceGetter.Get(version, apiKey); parser = ApiResponseParserFactory.Create(version); var tierResolver = PeriodSupportedTierResolver.Create(client, parser); taskHelper = new ApiTaskHelper(tierResolver); - processor = new SemaphoreTaskProcessor(maxDegreeOfParallelism); this.isOndemandEndpointEnabled = useOndemandEndpoint; } - public BuffettCodeApiTaskProcessor UpdateIfNeeded(string apiKey, uint maxDegreeOfParallelism, bool useOndemandEndpoint) + public BuffettCodeApiTaskProcessor UpdateIfNeeded(string apiKey, bool useOndemandEndpoint) { lock (updateLock) { @@ -39,15 +35,9 @@ public BuffettCodeApiTaskProcessor UpdateIfNeeded(string apiKey, uint maxDegreeO { client.UpdateApiKey(apiKey); } - if (!processor.GetMaxDegreeOfParallelism().Equals - (maxDegreeOfParallelism)) + if (isOndemandEndpointEnabled != useOndemandEndpoint) { - processor.UpdateMaxDegreeOfParallelism(maxDegreeOfParallelism); - } - - if (this.isOndemandEndpointEnabled != useOndemandEndpoint) - { - this.isOndemandEndpointEnabled = useOndemandEndpoint; + isOndemandEndpointEnabled = useOndemandEndpoint; } } return this; @@ -55,8 +45,8 @@ public BuffettCodeApiTaskProcessor UpdateIfNeeded(string apiKey, uint maxDegreeO public IApiResource GetApiResource(DataTypeConfig dataType, string ticker, IPeriod period, bool isConfigureAwait = true, bool useCache = true) { - var useOndemand = taskHelper.ShouldUseOndemandEndpoint(dataType, ticker, period, isOndemandEndpointEnabled); - var json = processor.Process(client.Get(dataType, ticker, period, useOndemand, isConfigureAwait, useCache)); + var useOndemand = taskHelper.ShouldUseOndemandEndpoint(dataType, ticker, period, isOndemandEndpointEnabled, isConfigureAwait, useCache); + var json = client.Get(dataType, ticker, period, useOndemand, isConfigureAwait, useCache); return parser.Parse(dataType, json); } @@ -66,18 +56,18 @@ public IList GetApiResources(DataTypeConfig dataType, string ticke { throw new ArgumentException($"from={from} is more than to={to}"); } - var endOfOndemandPeriod = taskHelper.FindEndOfOndemandPeriod(dataType, ticker, PeriodRange.Create(from, to), isOndemandEndpointEnabled); + var endOfOndemandPeriod = taskHelper.FindEndOfOndemandPeriod(dataType, ticker, PeriodRange.Create(from, to), isOndemandEndpointEnabled, isConfigureAwait, useCache); // use fixed tier from all range if (endOfOndemandPeriod is null) { - var json = processor.Process(client.GetRange(dataType, ticker, from, to, false, isConfigureAwait, useCache)); + var json = client.GetRange(dataType, ticker, from, to, false, isConfigureAwait, useCache); return parser.ParseRange(dataType, json); } else { - var ondemandTierJson = processor.Process(client.GetRange(dataType, ticker, from, endOfOndemandPeriod, true, isConfigureAwait, useCache)); - var fixedTierJson = processor.Process(client.GetRange(dataType, ticker, endOfOndemandPeriod.Next(), to, false, isConfigureAwait, useCache)); + var ondemandTierJson = client.GetRange(dataType, ticker, from, endOfOndemandPeriod, true, isConfigureAwait, useCache); + var fixedTierJson = client.GetRange(dataType, ticker, endOfOndemandPeriod.Next(), to, false, isConfigureAwait, useCache); var ondemandResources = parser.ParseRange(dataType, ondemandTierJson); var fixedTierResource = parser.ParseRange(dataType, fixedTierJson); diff --git a/BuffettCodeIO/BuffettCodeIO.csproj b/BuffettCodeIO/BuffettCodeIO.csproj index 5ef180c..99a7a99 100644 --- a/BuffettCodeIO/BuffettCodeIO.csproj +++ b/BuffettCodeIO/BuffettCodeIO.csproj @@ -98,8 +98,6 @@ - - diff --git a/BuffettCodeIO/Processor/ITaskProcessor.cs b/BuffettCodeIO/Processor/ITaskProcessor.cs deleted file mode 100644 index e314541..0000000 --- a/BuffettCodeIO/Processor/ITaskProcessor.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Threading.Tasks; - -namespace BuffettCodeIO.Processor -{ - /// - /// タスクプロセッサインタフェース - /// - /// - /// WebAPIのAPIコールを実行するTaskを一元的に受け取り、同時実行数の制御をするためのインタフェース。 - /// 以下のようなモチベーションで作成してます。 - /// - /// - /// ユーザのPCスペックによってはExcelアドインの実行でPCに負担が掛かるかもしれず、 - /// その場合に負荷を抑えたい - /// - /// - /// バフェットコードのWebAPIには「秒あたりxx件」という形でスロットリングの制限があり、 - /// この制限に引っかかる場合に実行ペースを一定以下にしたい。 - /// - /// - /// - interface ITaskProcessor - { - /// - /// タスクを実行します。 - /// - /// タスク - /// タスクの実行結果 - Type Process(Task task); - void UpdateMaxDegreeOfParallelism(uint maxDegreeOfParallelism); - - uint GetMaxDegreeOfParallelism(); - } -} \ No newline at end of file diff --git a/BuffettCodeIO/Processor/SemaphoreTaskProcessor.cs b/BuffettCodeIO/Processor/SemaphoreTaskProcessor.cs deleted file mode 100644 index 47c6eba..0000000 --- a/BuffettCodeIO/Processor/SemaphoreTaskProcessor.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace BuffettCodeIO.Processor -{ - /// - /// を利用した - /// - /// Taskの型 - public class SemaphoreTaskProcessor : ITaskProcessor - { - private SemaphoreSlim semaphore; - private uint maxDegreeOfParallelism; - - /// - /// コンストラクタ - /// - /// 同時に実行可能なタスクの数 - public SemaphoreTaskProcessor(uint maxDegreeOfParallelism) - { - this.maxDegreeOfParallelism = maxDegreeOfParallelism; - semaphore = new SemaphoreSlim((int)maxDegreeOfParallelism); - } - - public void UpdateMaxDegreeOfParallelism(uint maxDegreeOfParallelism) - { - this.maxDegreeOfParallelism = maxDegreeOfParallelism; - semaphore = new SemaphoreSlim((int)maxDegreeOfParallelism); - } - - public uint GetMaxDegreeOfParallelism() => maxDegreeOfParallelism; - - /// - public Type Process(Task task) - { - return CreateWrapperTask(task).Result; - } - - private Task CreateWrapperTask(Task task) - { - return Task.Run(async () => - { - try - { - await semaphore.WaitAsync(); - return task.Result; - } - finally - { - semaphore.Release(); - } - }); - } - } -} \ No newline at end of file diff --git a/BuffettCodeIO/Resolver/PeriodSupportedTierResolver.cs b/BuffettCodeIO/Resolver/PeriodSupportedTierResolver.cs index 4339e0c..2f94cdb 100644 --- a/BuffettCodeIO/Resolver/PeriodSupportedTierResolver.cs +++ b/BuffettCodeIO/Resolver/PeriodSupportedTierResolver.cs @@ -23,28 +23,28 @@ public PeriodSupportedTierResolver(IBuffettCodeApiClient apiClient, IApiResponse public static PeriodSupportedTierResolver Create(IBuffettCodeApiClient apiClient, IApiResponseParser parser) => new PeriodSupportedTierResolver(apiClient, parser, new SupportedTierDictionary()); - public SupportedTier Resolve(DataTypeConfig dataType, string ticker, IPeriod period) + public SupportedTier Resolve(DataTypeConfig dataType, string ticker, IPeriod period, bool isConfigureAwait, bool useCache) { switch (dataType) { case DataTypeConfig.Quarter: - return ResolveQuarter(ticker, period as FiscalQuarterPeriod); + return ResolveQuarter(ticker, period as FiscalQuarterPeriod, isConfigureAwait, useCache); default: throw new NotSupportedDataTypeException($"dataType={dataType} is not supported."); } } - private Company GetCompany(string ticker) + private Company GetCompany(string ticker, bool isConfigureAwait, bool useCache) { - var json = apiClient.Get(DataTypeConfig.Company, ticker, Snapshot.GetInstance(), false, true, true).Result; + var json = apiClient.Get(DataTypeConfig.Company, ticker, Snapshot.GetInstance(), false, isConfigureAwait, useCache); return parser.Parse(DataTypeConfig.Company, json) as Company; } - public SupportedTier ResolveQuarter(string ticker, FiscalQuarterPeriod period) + private SupportedTier ResolveQuarter(string ticker, FiscalQuarterPeriod period, bool isConfigureAwait, bool useCache) { if (!supportedTierDict.Has(ticker, DataTypeConfig.Quarter)) { - var company = GetCompany(ticker); + var company = GetCompany(ticker, isConfigureAwait, useCache); supportedTierDict.Add(company); } return supportedTierDict.Get(ticker, period); diff --git a/BuffettCodeIOTests/ApiTaskHelperTests.cs b/BuffettCodeIOTests/ApiTaskHelperTests.cs index 3cabf4f..97e4197 100644 --- a/BuffettCodeIOTests/ApiTaskHelperTests.cs +++ b/BuffettCodeIOTests/ApiTaskHelperTests.cs @@ -35,22 +35,22 @@ public void FindAvailableTierTest() var helper = new ApiTaskHelper(tierResolver); // enable ondemand endpoint - Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedOldest, true)); - Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod, true)); - Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedLatest, true)); - Assert.AreEqual(SupportedTier.OndemandTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandOldest, true)); - Assert.AreEqual(SupportedTier.OndemandTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod, true)); - Assert.AreEqual(SupportedTier.OndemandTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandLatest, true)); - Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod, true)); + Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedOldest, true, true, true)); + Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod, true, true, true)); + Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedLatest, true, true, true)); + Assert.AreEqual(SupportedTier.OndemandTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandOldest, true, true, true)); + Assert.AreEqual(SupportedTier.OndemandTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod, true, true, true)); + Assert.AreEqual(SupportedTier.OndemandTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandLatest, true, true, true)); + Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod, true, true, true)); // disabled ondemand endpoint - Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedOldest, false)); - Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod, false)); - Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedLatest, false)); - Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandOldest, false)); - Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod, false)); - Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandLatest, false)); - Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod, false)); + Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedOldest, false, true, true)); + Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod, false, true, true)); + Assert.AreEqual(SupportedTier.FixedTier, helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, fixedLatest, false, true, true)); + Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandOldest, false, true, true)); + Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod, false, true, true)); + Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandLatest, false, true, true)); + Assert.ThrowsException(() => helper.FindAvailableTier(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod, false, true, true)); } [TestMethod()] @@ -60,26 +60,26 @@ public void ShouldUseOndemandEndpointTest() var helper = new ApiTaskHelper(tierResolver); // enable ondemand endpoint - Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedOldest, true)); - Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod, true)); - Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedLatest, true)); - Assert.IsTrue(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandOldest, true)); - Assert.IsTrue(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod, true)); - Assert.IsTrue(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandLatest, true)); - Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod, true)); + Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedOldest, true, true, true)); + Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod, true, true, true)); + Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedLatest, true, true, true)); + Assert.IsTrue(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandOldest, true, true, true)); + Assert.IsTrue(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod, true, true, true)); + Assert.IsTrue(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandLatest, true, true, true)); + Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod, true, true, true)); // disabled ondemand endpoint - Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedOldest, false)); - Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod, false)); - Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedLatest, false)); - Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandOldest, false)); - Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod, false)); - Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandLatest, false)); - Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod, false)); + Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedOldest, false, true, true)); + Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod, false, true, true)); + Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, fixedLatest, false, true, true)); + Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandOldest, false, true, true)); + Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod, false, true, true)); + Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandLatest, false, true, true)); + Assert.ThrowsException(() => helper.ShouldUseOndemandEndpoint(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod, false, true, true)); // indicator endpoint - Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Indicator, ticker, null, false)); - Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Indicator, ticker, null, true)); + Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Indicator, ticker, null, false, true, true)); + Assert.IsFalse(helper.ShouldUseOndemandEndpoint(DataTypeConfig.Indicator, ticker, null, true, true, true)); } [TestMethod()] @@ -90,14 +90,14 @@ public void FindEndOfOndemandPeriodTest() // for fixed tier range, return null var tierRange = PeriodRange.Create(fixedOldest, fixedLatest); - Assert.IsNull(helper.FindEndOfOndemandPeriod(DataTypeConfig.Quarter, ticker, tierRange, true) as FiscalQuarterPeriod); - Assert.IsNull(helper.FindEndOfOndemandPeriod(DataTypeConfig.Quarter, ticker, tierRange, false) as FiscalQuarterPeriod); + Assert.IsNull(helper.FindEndOfOndemandPeriod(DataTypeConfig.Quarter, ticker, tierRange, true, true, true) as FiscalQuarterPeriod); + Assert.IsNull(helper.FindEndOfOndemandPeriod(DataTypeConfig.Quarter, ticker, tierRange, false, true, true) as FiscalQuarterPeriod); // for ondemand range tierRange = PeriodRange.Create(ondemandOldest, fixedLatest); - Assert.AreEqual(FiscalQuarterPeriod.Create(2016, 1), helper.FindEndOfOndemandPeriod(DataTypeConfig.Quarter, ticker, tierRange, true) as FiscalQuarterPeriod); - Assert.ThrowsException(() => helper.FindEndOfOndemandPeriod(DataTypeConfig.Quarter, ticker, tierRange, false)); + Assert.AreEqual(FiscalQuarterPeriod.Create(2016, 1), helper.FindEndOfOndemandPeriod(DataTypeConfig.Quarter, ticker, tierRange, true, true, true) as FiscalQuarterPeriod); + Assert.ThrowsException(() => helper.FindEndOfOndemandPeriod(DataTypeConfig.Quarter, ticker, tierRange, false, true, true)); } } } \ No newline at end of file diff --git a/BuffettCodeIOTests/Resolver/PeriodSupportedTierResolverTests.cs b/BuffettCodeIOTests/Resolver/PeriodSupportedTierResolverTests.cs index 089dc66..7e3d6f2 100644 --- a/BuffettCodeIOTests/Resolver/PeriodSupportedTierResolverTests.cs +++ b/BuffettCodeIOTests/Resolver/PeriodSupportedTierResolverTests.cs @@ -18,22 +18,6 @@ public class PeriodSupportedTierResolverTests private static readonly Company company = Company.Create(ticker, fixedTierRange, ondemandTierRange, PropertyDictionary.Empty(), PropertyDescriptionDictionary.Empty()); - [TestMethod()] - public void ResolveTest() - { - var supportedDict = new SupportedTierDictionary(); - supportedDict.Add(company); - var resolver = new PeriodSupportedTierResolver(null, null, supportedDict); - - Assert.AreEqual(SupportedTier.FixedTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, fixedOldest)); - Assert.AreEqual(SupportedTier.FixedTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod)); - Assert.AreEqual(SupportedTier.FixedTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, fixedLatest)); - Assert.AreEqual(SupportedTier.OndemandTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, ondemandOldest)); - Assert.AreEqual(SupportedTier.OndemandTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod)); - Assert.AreEqual(SupportedTier.OndemandTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, ondemandLatest)); - Assert.AreEqual(SupportedTier.None, resolver.Resolve(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod)); - } - [TestMethod()] public void ResolveQuarterTest() { @@ -41,13 +25,14 @@ public void ResolveQuarterTest() supportedDict.Add(company); var resolver = new PeriodSupportedTierResolver(null, null, supportedDict); - Assert.AreEqual(SupportedTier.FixedTier, resolver.ResolveQuarter(ticker, fixedOldest)); - Assert.AreEqual(SupportedTier.FixedTier, resolver.ResolveQuarter(ticker, fixedOldest.Next() as FiscalQuarterPeriod)); - Assert.AreEqual(SupportedTier.FixedTier, resolver.ResolveQuarter(ticker, fixedLatest)); - Assert.AreEqual(SupportedTier.OndemandTier, resolver.ResolveQuarter(ticker, ondemandOldest)); - Assert.AreEqual(SupportedTier.OndemandTier, resolver.ResolveQuarter(ticker, ondemandOldest.Next() as FiscalQuarterPeriod)); - Assert.AreEqual(SupportedTier.OndemandTier, resolver.ResolveQuarter(ticker, ondemandLatest)); - Assert.AreEqual(SupportedTier.None, resolver.ResolveQuarter(ticker, ondemandLatest.Next() as FiscalQuarterPeriod)); + Assert.AreEqual(SupportedTier.FixedTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, fixedOldest, false, false)); + Assert.AreEqual(SupportedTier.FixedTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, fixedOldest.Next() as FiscalQuarterPeriod, false, false)); + Assert.AreEqual(SupportedTier.FixedTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, fixedLatest, false, false)); + Assert.AreEqual(SupportedTier.OndemandTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, ondemandOldest, false, false)); + Assert.AreEqual(SupportedTier.OndemandTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, ondemandOldest.Next() as FiscalQuarterPeriod, false, false)); + Assert.AreEqual(SupportedTier.OndemandTier, resolver.Resolve(DataTypeConfig.Quarter, ticker, ondemandLatest, false, false)); + Assert.AreEqual(SupportedTier.None, resolver.Resolve(DataTypeConfig.Quarter, ticker, ondemandLatest.Next() as FiscalQuarterPeriod, false, false)); } + } } \ No newline at end of file