Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3d1ea19
Added caching to GraphQL connections.
grokys Jan 7, 2019
b046d6d
Implement refreshing PR list and details.
grokys Jan 16, 2019
bbdcf99
Merge branch 'master' into feature/graphql-caching
StanleyGoldman Feb 27, 2019
14eadbe
Update submodule
StanleyGoldman Feb 27, 2019
ff484ab
Fixing compilation errors
StanleyGoldman Feb 28, 2019
c3b597f
Passing through the call to refresh
StanleyGoldman Feb 28, 2019
4d140c6
Merge remote-tracking branch 'remotes/origin/master' into feature/gra…
StanleyGoldman Mar 8, 2019
81ed3b4
Cache assignable users for an hour
StanleyGoldman Mar 8, 2019
63c829c
Merge branch 'master' into feature/graphql-caching
StanleyGoldman Mar 14, 2019
00da02c
Caching ReadViewer for 10 minutes
StanleyGoldman Mar 14, 2019
e9395e8
Adding functionality to refresh user repository list
StanleyGoldman Mar 14, 2019
ade3b5f
Ensuring the first load is never cached
StanleyGoldman Mar 21, 2019
f7e42eb
Disabling caching for the repository clone view
StanleyGoldman Mar 26, 2019
e6290ba
Merge remote-tracking branch 'origin/master' into feature/graphql-cac…
StanleyGoldman Mar 27, 2019
6c9874b
Fixes needed after merge
StanleyGoldman Mar 27, 2019
895bbb2
Fixing some tests
StanleyGoldman Mar 27, 2019
098cd3f
Fixing tests
StanleyGoldman Mar 28, 2019
0f46686
Fixing tests
StanleyGoldman Mar 28, 2019
415e7dd
Changing code to use Task.Run over TaskScheduler.Default
StanleyGoldman Apr 4, 2019
c8ace03
Merge remote-tracking branch 'origin/master' into feature/graphql-cac…
StanleyGoldman Apr 4, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Added caching to GraphQL connections.
  • Loading branch information
grokys committed Jan 7, 2019
commit 3d1ea19fc911b77513b6363f21e4d5841e7bf681
2 changes: 2 additions & 0 deletions src/GitHub.Api/GitHub.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<ItemGroup>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.Caching" />
</ItemGroup>

<ItemGroup>
Expand All @@ -30,6 +31,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FileCache.Signed" Version="2.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.1" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>
Expand Down
127 changes: 127 additions & 0 deletions src/GitHub.Api/GraphQLClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.Caching;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using Octokit.GraphQL;
using Octokit.GraphQL.Core;

namespace GitHub.Api
{
public class GraphQLClient : IGraphQLClient
{
public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromHours(8);
readonly IConnection connection;
readonly ObjectCache cache;

public GraphQLClient(
IConnection connection,
ObjectCache cache)
{
this.connection = connection;
this.cache = cache;
}

public Task<T> Run<T>(
IQueryableValue<T> query,
Dictionary<string, object> variables = null,
bool refresh = false,
TimeSpan? cacheDuration = null,
CancellationToken cancellationToken = default)
{
return Run(query.Compile(), variables, refresh, cacheDuration, cancellationToken);
}

public Task<IEnumerable<T>> Run<T>(
IQueryableList<T> query,
Dictionary<string, object> variables = null,
bool refresh = false,
TimeSpan? cacheDuration = null,
CancellationToken cancellationToken = default)
{
return Run(query.Compile(), variables, refresh, cacheDuration, cancellationToken);
}

public async Task<T> Run<T>(
ICompiledQuery<T> query,
Dictionary<string, object> variables = null,
bool refresh = false,
TimeSpan? cacheDuration = null,
CancellationToken cancellationToken = default)
{
if (!query.IsMutation)
{
var wrapper = new CachingWrapper(this, cacheDuration ?? DefaultCacheDuration, refresh);
return await wrapper.Run(query, variables, cancellationToken);
}
else
{
return await connection.Run(query, variables, cancellationToken);
}
}

static string GetHash(string input)
{
var sb = new StringBuilder();

using (var hash = SHA256.Create())
{
var result = hash.ComputeHash(Encoding.UTF8.GetBytes(input));

foreach (var b in result)
{
sb.Append(b.ToString("x2", CultureInfo.InvariantCulture));
}
}

return sb.ToString();
}

class CachingWrapper : IConnection
{
readonly GraphQLClient owner;
readonly TimeSpan cacheDuration;
readonly bool refresh;

public CachingWrapper(
GraphQLClient owner,
TimeSpan cacheDuration,
bool refresh)
{
this.owner = owner;
this.cacheDuration = cacheDuration;
this.refresh = refresh;
}

public Uri Uri => owner.connection.Uri;

public async Task<string> Run(string query, CancellationToken cancellationToken = default)
{
// Switch to background thread because ObjectCache does not provide an async API.
await TaskScheduler.Default;

var hash = GetHash(query);

if (refresh)
{
owner.cache.Remove(hash, Uri.Host);
}

var data = (string)owner.cache.Get(hash, Uri.Host);

if (data != null)
{
return data;
}

var result = await owner.connection.Run(query, cancellationToken);
owner.cache.Add(hash, result, DateTimeOffset.Now + cacheDuration, Uri.Host);
return result;
}
}
}
}
15 changes: 13 additions & 2 deletions src/GitHub.Api/GraphQLClientFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Runtime.Caching;
using System.Threading.Tasks;
using GitHub.Info;
using GitHub.Models;
using GitHub.Primitives;
using Octokit.GraphQL;
Expand All @@ -17,6 +20,7 @@ public class GraphQLClientFactory : IGraphQLClientFactory
{
readonly IKeychain keychain;
readonly IProgram program;
readonly FileCache cache;

/// <summary>
/// Initializes a new instance of the <see cref="GraphQLClientFactory"/> class.
Expand All @@ -28,14 +32,21 @@ public GraphQLClientFactory(IKeychain keychain, IProgram program)
{
this.keychain = keychain;
this.program = program;

var cachePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
ApplicationInfo.ApplicationName,
"GraphQLCache");
cache = new FileCache(cachePath);
}

/// <inheirtdoc/>
public Task<Octokit.GraphQL.IConnection> CreateConnection(HostAddress address)
public Task<IGraphQLClient> CreateConnection(HostAddress address)
{
var credentials = new GraphQLKeychainCredentialStore(keychain, address);
var header = new ProductHeaderValue(program.ProductHeader.Name, program.ProductHeader.Version);
return Task.FromResult<Octokit.GraphQL.IConnection>(new Connection(header, address.GraphQLUri, credentials));
var connection = new Connection(header, address.GraphQLUri, credentials);
return Task.FromResult<IGraphQLClient>(new GraphQLClient(connection, cache));
}
}
}
33 changes: 33 additions & 0 deletions src/GitHub.Api/IGraphQLClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Octokit.GraphQL;
using Octokit.GraphQL.Core;

namespace GitHub.Api
{
public interface IGraphQLClient
{
Task<T> Run<T>(
IQueryableValue<T> query,
Dictionary<string, object> variables = null,
bool refresh = false,
TimeSpan? cacheDuration = null,
CancellationToken cancellationToken = default);

Task<IEnumerable<T>> Run<T>(
IQueryableList<T> query,
Dictionary<string, object> variables = null,
bool refresh = false,
TimeSpan? cacheDuration = null,
CancellationToken cancellationToken = default);

Task<T> Run<T>(
ICompiledQuery<T> query,
Dictionary<string, object> variables = null,
bool refresh = false,
TimeSpan? cacheDuration = null,
CancellationToken cancellationToken = default);
}
}
9 changes: 4 additions & 5 deletions src/GitHub.Api/IGraphQLClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
namespace GitHub.Api
{
/// <summary>
/// Creates GraphQL <see cref="Octokit.GraphQL.IConnection"/>s for querying the
/// GitHub GraphQL API.
/// Creates <see cref="IGraphQLClient"/>s for querying the GitHub GraphQL API.
/// </summary>
public interface IGraphQLClientFactory
{
/// <summary>
/// Creates a new <see cref="Octokit.GraphQL.IConnection"/>.
/// Creates a new <see cref="IGraphQLClient"/>.
/// </summary>
/// <param name="address">The address of the server.</param>
/// <returns>A task returning the created connection.</returns>
Task<Octokit.GraphQL.IConnection> CreateConnection(HostAddress address);
/// <returns>A task returning the created client.</returns>
Task<IGraphQLClient> CreateConnection(HostAddress address);
}
}