Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 3d1ea19

Browse files
committed
Added caching to GraphQL connections.
1 parent 645db55 commit 3d1ea19

File tree

6 files changed

+180
-8
lines changed

6 files changed

+180
-8
lines changed

src/GitHub.Api/GitHub.Api.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<ItemGroup>
1818
<Reference Include="System.ComponentModel.Composition" />
1919
<Reference Include="System.Net.Http" />
20+
<Reference Include="System.Runtime.Caching" />
2021
</ItemGroup>
2122

2223
<ItemGroup>
@@ -30,6 +31,7 @@
3031
</ItemGroup>
3132

3233
<ItemGroup>
34+
<PackageReference Include="FileCache.Signed" Version="2.2.0" />
3335
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.1" />
3436
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
3537
</ItemGroup>

src/GitHub.Api/GraphQLClient.cs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Runtime.Caching;
5+
using System.Security.Cryptography;
6+
using System.Text;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.VisualStudio.Threading;
10+
using Octokit.GraphQL;
11+
using Octokit.GraphQL.Core;
12+
13+
namespace GitHub.Api
14+
{
15+
public class GraphQLClient : IGraphQLClient
16+
{
17+
public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromHours(8);
18+
readonly IConnection connection;
19+
readonly ObjectCache cache;
20+
21+
public GraphQLClient(
22+
IConnection connection,
23+
ObjectCache cache)
24+
{
25+
this.connection = connection;
26+
this.cache = cache;
27+
}
28+
29+
public Task<T> Run<T>(
30+
IQueryableValue<T> query,
31+
Dictionary<string, object> variables = null,
32+
bool refresh = false,
33+
TimeSpan? cacheDuration = null,
34+
CancellationToken cancellationToken = default)
35+
{
36+
return Run(query.Compile(), variables, refresh, cacheDuration, cancellationToken);
37+
}
38+
39+
public Task<IEnumerable<T>> Run<T>(
40+
IQueryableList<T> query,
41+
Dictionary<string, object> variables = null,
42+
bool refresh = false,
43+
TimeSpan? cacheDuration = null,
44+
CancellationToken cancellationToken = default)
45+
{
46+
return Run(query.Compile(), variables, refresh, cacheDuration, cancellationToken);
47+
}
48+
49+
public async Task<T> Run<T>(
50+
ICompiledQuery<T> query,
51+
Dictionary<string, object> variables = null,
52+
bool refresh = false,
53+
TimeSpan? cacheDuration = null,
54+
CancellationToken cancellationToken = default)
55+
{
56+
if (!query.IsMutation)
57+
{
58+
var wrapper = new CachingWrapper(this, cacheDuration ?? DefaultCacheDuration, refresh);
59+
return await wrapper.Run(query, variables, cancellationToken);
60+
}
61+
else
62+
{
63+
return await connection.Run(query, variables, cancellationToken);
64+
}
65+
}
66+
67+
static string GetHash(string input)
68+
{
69+
var sb = new StringBuilder();
70+
71+
using (var hash = SHA256.Create())
72+
{
73+
var result = hash.ComputeHash(Encoding.UTF8.GetBytes(input));
74+
75+
foreach (var b in result)
76+
{
77+
sb.Append(b.ToString("x2", CultureInfo.InvariantCulture));
78+
}
79+
}
80+
81+
return sb.ToString();
82+
}
83+
84+
class CachingWrapper : IConnection
85+
{
86+
readonly GraphQLClient owner;
87+
readonly TimeSpan cacheDuration;
88+
readonly bool refresh;
89+
90+
public CachingWrapper(
91+
GraphQLClient owner,
92+
TimeSpan cacheDuration,
93+
bool refresh)
94+
{
95+
this.owner = owner;
96+
this.cacheDuration = cacheDuration;
97+
this.refresh = refresh;
98+
}
99+
100+
public Uri Uri => owner.connection.Uri;
101+
102+
public async Task<string> Run(string query, CancellationToken cancellationToken = default)
103+
{
104+
// Switch to background thread because ObjectCache does not provide an async API.
105+
await TaskScheduler.Default;
106+
107+
var hash = GetHash(query);
108+
109+
if (refresh)
110+
{
111+
owner.cache.Remove(hash, Uri.Host);
112+
}
113+
114+
var data = (string)owner.cache.Get(hash, Uri.Host);
115+
116+
if (data != null)
117+
{
118+
return data;
119+
}
120+
121+
var result = await owner.connection.Run(query, cancellationToken);
122+
owner.cache.Add(hash, result, DateTimeOffset.Now + cacheDuration, Uri.Host);
123+
return result;
124+
}
125+
}
126+
}
127+
}

src/GitHub.Api/GraphQLClientFactory.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System;
22
using System.ComponentModel.Composition;
3+
using System.IO;
4+
using System.Runtime.Caching;
35
using System.Threading.Tasks;
6+
using GitHub.Info;
47
using GitHub.Models;
58
using GitHub.Primitives;
69
using Octokit.GraphQL;
@@ -17,6 +20,7 @@ public class GraphQLClientFactory : IGraphQLClientFactory
1720
{
1821
readonly IKeychain keychain;
1922
readonly IProgram program;
23+
readonly FileCache cache;
2024

2125
/// <summary>
2226
/// Initializes a new instance of the <see cref="GraphQLClientFactory"/> class.
@@ -28,14 +32,21 @@ public GraphQLClientFactory(IKeychain keychain, IProgram program)
2832
{
2933
this.keychain = keychain;
3034
this.program = program;
35+
36+
var cachePath = Path.Combine(
37+
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
38+
ApplicationInfo.ApplicationName,
39+
"GraphQLCache");
40+
cache = new FileCache(cachePath);
3141
}
3242

3343
/// <inheirtdoc/>
34-
public Task<Octokit.GraphQL.IConnection> CreateConnection(HostAddress address)
44+
public Task<IGraphQLClient> CreateConnection(HostAddress address)
3545
{
3646
var credentials = new GraphQLKeychainCredentialStore(keychain, address);
3747
var header = new ProductHeaderValue(program.ProductHeader.Name, program.ProductHeader.Version);
38-
return Task.FromResult<Octokit.GraphQL.IConnection>(new Connection(header, address.GraphQLUri, credentials));
48+
var connection = new Connection(header, address.GraphQLUri, credentials);
49+
return Task.FromResult<IGraphQLClient>(new GraphQLClient(connection, cache));
3950
}
4051
}
4152
}

src/GitHub.Api/IGraphQLClient.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Octokit.GraphQL;
6+
using Octokit.GraphQL.Core;
7+
8+
namespace GitHub.Api
9+
{
10+
public interface IGraphQLClient
11+
{
12+
Task<T> Run<T>(
13+
IQueryableValue<T> query,
14+
Dictionary<string, object> variables = null,
15+
bool refresh = false,
16+
TimeSpan? cacheDuration = null,
17+
CancellationToken cancellationToken = default);
18+
19+
Task<IEnumerable<T>> Run<T>(
20+
IQueryableList<T> query,
21+
Dictionary<string, object> variables = null,
22+
bool refresh = false,
23+
TimeSpan? cacheDuration = null,
24+
CancellationToken cancellationToken = default);
25+
26+
Task<T> Run<T>(
27+
ICompiledQuery<T> query,
28+
Dictionary<string, object> variables = null,
29+
bool refresh = false,
30+
TimeSpan? cacheDuration = null,
31+
CancellationToken cancellationToken = default);
32+
}
33+
}

src/GitHub.Api/IGraphQLClientFactory.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@
44
namespace GitHub.Api
55
{
66
/// <summary>
7-
/// Creates GraphQL <see cref="Octokit.GraphQL.IConnection"/>s for querying the
8-
/// GitHub GraphQL API.
7+
/// Creates <see cref="IGraphQLClient"/>s for querying the GitHub GraphQL API.
98
/// </summary>
109
public interface IGraphQLClientFactory
1110
{
1211
/// <summary>
13-
/// Creates a new <see cref="Octokit.GraphQL.IConnection"/>.
12+
/// Creates a new <see cref="IGraphQLClient"/>.
1413
/// </summary>
1514
/// <param name="address">The address of the server.</param>
16-
/// <returns>A task returning the created connection.</returns>
17-
Task<Octokit.GraphQL.IConnection> CreateConnection(HostAddress address);
15+
/// <returns>A task returning the created client.</returns>
16+
Task<IGraphQLClient> CreateConnection(HostAddress address);
1817
}
1918
}

0 commit comments

Comments
 (0)