Skip to content

Commit 48a4024

Browse files
bartizanwaldekmastykarzCopilot
authored
Optimize MSGraph OpenApi specs caching based on e-tag (#1379)
* Extract method to get an api URL * Arrange functions to get yaml and e-tag open api filenames * Correct some logging messages * Move SetDbJournaling() * Add second check if db should be updated * Extract IsModifiedToday function * Extract DownloadOpenAPIFileAsync() * Add conditional downloading by an e-tag * Add debug logging for last-write-time attr update * Prevent db generation if a spec update fails * Update DevProxy.Abstractions/Data/MSGraphDb.cs Co-authored-by: Copilot <[email protected]> * Update DevProxy.Abstractions/Data/MSGraphDb.cs Co-authored-by: Copilot <[email protected]> * Fix discarded variable declaration * Minor fixes --------- Co-authored-by: Waldek Mastykarz <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent ee9a94d commit 48a4024

File tree

1 file changed

+92
-20
lines changed

1 file changed

+92
-20
lines changed

DevProxy.Abstractions/Data/MSGraphDb.cs

Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using Microsoft.Extensions.Logging;
88
using Microsoft.OpenApi.Models;
99
using Microsoft.OpenApi.Readers;
10+
using System.Net;
11+
using System.Net.Http.Headers;
1012

1113
namespace DevProxy.Abstractions.Data;
1214

@@ -49,14 +51,29 @@ public async Task<int> GenerateDbAsync(bool skipIfUpdatedToday, CancellationToke
4951
try
5052
{
5153
var dbFileInfo = new FileInfo(MSGraphDbFilePath);
52-
var modifiedToday = dbFileInfo.Exists && dbFileInfo.LastWriteTime.Date == DateTime.Now.Date;
54+
var modifiedToday = IsModifiedToday(dbFileInfo);
5355
if (modifiedToday && skipIfUpdatedToday)
5456
{
55-
_logger.LogInformation("Microsoft Graph database already updated today");
57+
_logger.LogInformation("Microsoft Graph database has already been updated today");
58+
return 1;
59+
}
60+
61+
var (isApiModified, hasErrors) = await UpdateOpenAPIGraphFilesIfNecessaryAsync(appFolder, cancellationToken);
62+
63+
if (hasErrors)
64+
{
65+
_logger.LogWarning("Unable to update Microsoft Graph database");
66+
return 1;
67+
}
68+
69+
if (!isApiModified)
70+
{
71+
UpdateLastWriteTime(dbFileInfo);
72+
_logger.LogDebug("Updated the last-write-time attribute of Microsoft Graph database {File}", dbFileInfo);
73+
_logger.LogInformation("Microsoft Graph database is already up-to-date");
5674
return 1;
5775
}
5876

59-
await UpdateOpenAPIGraphFilesIfNecessaryAsync(appFolder, cancellationToken);
6077
await LoadOpenAPIFilesAsync(appFolder, cancellationToken);
6178
if (_openApiDocuments.Count < 1)
6279
{
@@ -65,24 +82,21 @@ public async Task<int> GenerateDbAsync(bool skipIfUpdatedToday, CancellationToke
6582
}
6683

6784
await CreateDbAsync(cancellationToken);
68-
69-
SetDbJournaling(false);
7085
await FillDataAsync(cancellationToken);
71-
SetDbJournaling(true);
72-
73-
_logger.LogInformation("Microsoft Graph database successfully updated");
7486

87+
_logger.LogInformation("Microsoft Graph database is successfully updated");
7588
return 0;
7689
}
7790
catch (Exception ex)
7891
{
7992
_logger.LogError(ex, "Error generating Microsoft Graph database");
8093
return 1;
8194
}
82-
8395
}
8496

85-
private static string GetGraphOpenApiYamlFileName(string version) => $"graph-{version.Replace(".", "_", StringComparison.OrdinalIgnoreCase)}-openapi.yaml";
97+
private static bool IsModifiedToday(FileInfo fileInfo) => fileInfo.Exists && fileInfo.LastWriteTime.Date == DateTime.Now.Date;
98+
99+
private static void UpdateLastWriteTime(FileInfo fileInfo) => fileInfo.LastWriteTime = DateTime.Now;
86100

87101
private async Task CreateDbAsync(CancellationToken cancellationToken)
88102
{
@@ -110,6 +124,8 @@ private async Task FillDataAsync(CancellationToken cancellationToken)
110124
{
111125
_logger.LogInformation("Filling database...");
112126

127+
SetDbJournaling(false);
128+
113129
await using var transaction = await Connection.BeginTransactionAsync(cancellationToken);
114130

115131
var i = 0;
@@ -158,38 +174,86 @@ private async Task FillDataAsync(CancellationToken cancellationToken)
158174

159175
await transaction.CommitAsync(cancellationToken);
160176

177+
SetDbJournaling(true);
178+
161179
_logger.LogInformation("Inserted {EndpointCount} endpoints in the database", i);
162180
}
163181

164-
private async Task UpdateOpenAPIGraphFilesIfNecessaryAsync(string folder, CancellationToken cancellationToken)
182+
private async Task<(bool isApiUpdated, bool hasErrors)> UpdateOpenAPIGraphFilesIfNecessaryAsync(string folder, CancellationToken cancellationToken)
165183
{
166184
_logger.LogInformation("Checking for updated OpenAPI files...");
167185

186+
var isApiUpdated = false;
187+
var hasErrors = false;
188+
168189
foreach (var version in graphVersions)
169190
{
170191
try
171192
{
172-
var file = new FileInfo(Path.Combine(folder, GetGraphOpenApiYamlFileName(version)));
173-
_logger.LogDebug("Checking for updated OpenAPI file {File}...", file);
174-
if (file.Exists && file.LastWriteTime.Date == DateTime.Now.Date)
193+
var yamlFile = new FileInfo(Path.Combine(folder, GetGraphOpenApiYamlFileName(version)));
194+
_logger.LogDebug("Checking for updated OpenAPI file {File}...", yamlFile);
195+
if (IsModifiedToday(yamlFile))
175196
{
176-
_logger.LogInformation("File {File} already updated today", file);
197+
_logger.LogInformation("File {File} has already been updated today", yamlFile);
177198
continue;
178199
}
179200

180-
var url = $"https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/{version}/openapi.yaml";
201+
var url = GetOpenApiSpecUrl(version);
181202
_logger.LogInformation("Downloading OpenAPI file from {Url}...", url);
182203

183-
var response = await _httpClient.GetStringAsync(url, cancellationToken);
184-
await File.WriteAllTextAsync(file.FullName, response, cancellationToken);
185-
186-
_logger.LogDebug("Downloaded OpenAPI file from {Url} to {File}", url, file);
204+
var etagFile = new FileInfo(Path.Combine(folder, GetGraphOpenApiEtagFileName(version)));
205+
isApiUpdated |= await DownloadOpenAPIFileAsync(url, yamlFile, etagFile, cancellationToken);
187206
}
188207
catch (Exception ex)
189208
{
209+
hasErrors = true;
190210
_logger.LogError(ex, "Error updating OpenAPI files");
191211
}
192212
}
213+
return (isApiUpdated, hasErrors);
214+
}
215+
216+
private async Task<bool> DownloadOpenAPIFileAsync(string url, FileInfo yamlFile, FileInfo etagFile, CancellationToken cancellationToken)
217+
{
218+
var tag = string.Empty;
219+
if (etagFile.Exists)
220+
{
221+
tag = await File.ReadAllTextAsync(etagFile.FullName, cancellationToken);
222+
}
223+
224+
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
225+
if (!string.IsNullOrWhiteSpace(tag))
226+
{
227+
requestMessage.Headers.IfNoneMatch.Add(new EntityTagHeaderValue(tag));
228+
}
229+
230+
var response = await _httpClient.SendAsync(requestMessage, cancellationToken);
231+
232+
if (response.StatusCode == HttpStatusCode.NotModified)
233+
{
234+
UpdateLastWriteTime(yamlFile);
235+
_logger.LogDebug("File {File} already up-to-date. Updated the last-write-time attribute", yamlFile);
236+
return false;
237+
}
238+
239+
// Save the new OpenAPI spec.
240+
_ = response.EnsureSuccessStatusCode();
241+
await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken);
242+
await using var fileStream = new FileStream(yamlFile.FullName,
243+
new FileStreamOptions { Mode = FileMode.Create, Access = FileAccess.Write, Share = FileShare.None });
244+
await contentStream.CopyToAsync(fileStream, cancellationToken);
245+
246+
if (response.Headers.ETag is not null)
247+
{
248+
await File.WriteAllTextAsync(etagFile.FullName, response.Headers.ETag.Tag, cancellationToken);
249+
}
250+
else
251+
{
252+
etagFile.Delete();
253+
}
254+
255+
_logger.LogDebug("Downloaded OpenAPI file from {Url} to {File}", url, yamlFile);
256+
return true;
193257
}
194258

195259
private async Task LoadOpenAPIFilesAsync(string folder, CancellationToken cancellationToken)
@@ -238,6 +302,14 @@ private void SetDbJournaling(bool enabled)
238302
}
239303
}
240304

305+
private static string GetOpenApiSpecUrl(string version) => $"https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/{version}/openapi.yaml";
306+
307+
private static string GetBaseGraphOpenApiFileName(string version) => $"graph-{version.Replace(".", "_", StringComparison.OrdinalIgnoreCase)}-openapi";
308+
309+
private static string GetGraphOpenApiYamlFileName(string version) => $"{GetBaseGraphOpenApiFileName(version)}.yaml";
310+
311+
private static string GetGraphOpenApiEtagFileName(string version) => $"{GetBaseGraphOpenApiFileName(version)}.etag.txt";
312+
241313
public void Dispose()
242314
{
243315
_connection?.Dispose();

0 commit comments

Comments
 (0)