Created
January 15, 2012 07:04
-
-
Save anaisbetts/1614805 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Diagnostics; | |
| using System.Globalization; | |
| using System.Linq; | |
| using System.Net; | |
| using System.Reactive.Linq; | |
| using System.Reflection; | |
| using System.Text; | |
| using System.Text.RegularExpressions; | |
| using Hammock; | |
| using Hammock.Authentication.Basic; | |
| using NLog; | |
| using ReactiveUI; | |
| namespace GitHub.Api | |
| { | |
| public class GitHubClient | |
| { | |
| static readonly Logger log = LogManager.GetCurrentClassLogger(); | |
| public const string DefaultAuthority = "https://api.github.com"; | |
| static readonly string userAgent; | |
| readonly string authority; | |
| readonly RestClient restClientOverride; | |
| static GitHubClient() | |
| { | |
| userAgent = string.Format("MyAwesomeApp/{0}", Assembly.GetExecutingAssembly().GetName().Version); | |
| } | |
| public GitHubClient(string authority = null) | |
| { | |
| this.authority = authority ?? DefaultAuthority; | |
| } | |
| public GitHubClient(RestClient restClientOverrideForTestingPurposesOnly) | |
| { | |
| Ensure.ArgumentNotNull(restClientOverrideForTestingPurposesOnly); | |
| restClientOverride = restClientOverrideForTestingPurposesOnly; | |
| } | |
| public int RateLimit { get; private set; } | |
| public int RateLimitRemaining { get; private set; } | |
| RestClient RestClient | |
| { | |
| get | |
| { | |
| if (restClientOverride != null) | |
| { | |
| return restClientOverride; | |
| } | |
| return new RestClient | |
| { | |
| Authority = authority, | |
| Credentials = new BasicAuthCredentials | |
| { | |
| Username = /* TODO: Fetch the username from somewhere */ | |
| Password = /* TODO: And the Password */ | |
| }, | |
| UserAgent = userAgent, | |
| Deserializer = new HammockJsonDotNetSerializer(), | |
| Serializer = new HammockJsonDotNetSerializer() | |
| }; | |
| } | |
| } | |
| public IObservable<RestResponse<GitHubRepository>> CreateRepository(GitHubRepository repo, string orgLogin = null) | |
| { | |
| var request = new RestRequest | |
| { | |
| Path = orgLogin == null ? "user/repos" : string.Format("orgs/{0}/repos", orgLogin), | |
| Entity = new | |
| { | |
| name = repo.Name, | |
| @public = !repo.Private, | |
| description = repo.Description, | |
| has_issues = repo.HasIssues, | |
| has_downloads = repo.HasDownloads, | |
| has_wiki = repo.HasWiki, | |
| } | |
| }; | |
| return RestClient.RequestAsync<GitHubRepository>(request); | |
| } | |
| public IObservable<RestResponse<Dictionary<string, string>>> GetEmojis() | |
| { | |
| return ExecuteRequestAsync<Dictionary<string, string>>("emojis"); | |
| } | |
| public IObservable<RestResponse<GitHubOrganization>> GetOrganization(string login) | |
| { | |
| Ensure.ArgumentNotNullOrEmptyString(login); | |
| return ExecuteRequestAsync<GitHubOrganization>(string.Format("orgs/{0}", login)); | |
| } | |
| public IObservable<RestResponse<GitHubParticipation>> GetParticipation(string nameWithOwner) | |
| { | |
| Ensure.ArgumentNotNullOrEmptyString(nameWithOwner); | |
| return ExecuteRequestAsync<GitHubParticipation>(String.Format("{0}/graphs/participation", nameWithOwner)); | |
| } | |
| IObservable<RestResponse<TResponse>> ExecuteRequestAsync<TResponse>(string path) where TResponse : class | |
| { | |
| var request = new RestRequest { Path = path }; | |
| var response = RestClient.RequestAsync<TResponse>(request); | |
| return response.Do(resp => | |
| { | |
| RateLimit = resp.Headers["X-RateLimit-Limit"].ToInt32(); | |
| RateLimitRemaining = resp.Headers["X-RateLimit-Remaining"].ToInt32(); | |
| }); | |
| } | |
| } | |
| public class GitHubRepository | |
| { | |
| public string CloneUrl { get; set; } | |
| public DateTime CreatedAt { get; set; } | |
| public string Description { get; set; } | |
| public bool Fork { get; set; } | |
| public int Forks { get; set; } | |
| public string GitUrl { get; set; } | |
| public bool HasDownloads { get; set; } | |
| public bool HasIssues { get; set; } | |
| public bool HasWiki { get; set; } | |
| public string Homepage { get; set; } | |
| public string HtmlUrl { get; set; } | |
| public int Id { get; set; } | |
| public string Language { get; set; } | |
| public string MasterBranch { get; set; } | |
| public string Name { get; set; } | |
| public int OpenIssues { get; set; } | |
| public GitHubUser Owner { get; set; } | |
| public bool Private { get; set; } | |
| public DateTime? PushedAt { get; set; } | |
| public int Size { get; set; } | |
| public string SshUrl { get; set; } | |
| public string SvnUrl { get; set; } | |
| public string Url { get; set; } | |
| public int Watchers { get; set; } | |
| } | |
| public static class HammockObservableMixins | |
| { | |
| static readonly Logger log = LogManager.GetCurrentClassLogger(); | |
| public static IObservable<RestResponse> RequestAsync(this RestClient client, RestRequest request, int timeoutSeconds = 15) | |
| { | |
| var ret = new AsyncSubject<RestResponse>(); | |
| client.BeginRequest(request, (rq, resp, state) => | |
| { | |
| try | |
| { | |
| log.Info(resp.ResponseUri); | |
| if (log.IsDebugEnabled) | |
| resp.Headers.AllKeys.ForEach(x => log.Debug("{0}: {1}", x, resp.Headers[x])); | |
| ret.OnNext(resp); | |
| ret.OnCompleted(); | |
| } | |
| catch (Exception ex) | |
| { | |
| ret.OnError(ex); | |
| } | |
| }); | |
| return ret | |
| .Timeout(TimeSpan.FromSeconds(timeoutSeconds), RxApp.TaskpoolScheduler) | |
| .ThrowOnRestResponseFailure(); | |
| } | |
| public static IObservable<RestResponse<T>> RequestAsync<T>(this RestClient client, RestRequest request, int timeoutSeconds = 15) | |
| { | |
| var ret = new AsyncSubject<RestResponse<T>>(); | |
| client.BeginRequest<T>(request, (rq, resp, state) => | |
| { | |
| try | |
| { | |
| log.Info(resp.ResponseUri); | |
| if(log.IsDebugEnabled) | |
| resp.Headers.AllKeys.ForEach(x => log.Debug("{0}: {1}", x, resp.Headers[x])); | |
| ret.OnNext(resp); | |
| ret.OnCompleted(); | |
| } | |
| catch (Exception ex) | |
| { | |
| ret.OnError(ex); | |
| } | |
| }); | |
| return ret | |
| .Timeout(TimeSpan.FromSeconds(timeoutSeconds), RxApp.TaskpoolScheduler) | |
| .ThrowOnRestResponseFailure(); | |
| } | |
| public static IObservable<T> ThrowOnRestResponseFailure<T>(this IObservable<T> observable) | |
| where T : RestResponseBase | |
| { | |
| return observable.SelectMany(x => | |
| { | |
| if (x == null) | |
| { | |
| return Observable.Return(x); | |
| } | |
| if (x.InnerException != null) | |
| { | |
| return Observable.Throw<T>(x.InnerException); | |
| } | |
| if ((int)x.StatusCode >= 400) | |
| { | |
| return Observable.Throw<T>(new WebException(x.Content)); | |
| } | |
| return Observable.Return(x); | |
| }); | |
| } | |
| } | |
| public class HammockJsonDotNetSerializer : ISerializer, IDeserializer | |
| { | |
| readonly JsonSerializerSettings settings; | |
| public HammockJsonDotNetSerializer() | |
| : this(new JsonSerializerSettings { ContractResolver = new CustomContractResolver() }) | |
| { | |
| } | |
| public HammockJsonDotNetSerializer(JsonSerializerSettings settings) | |
| { | |
| this.settings = settings; | |
| } | |
| #region IDeserializer Members | |
| public object Deserialize(RestResponseBase response, Type type) | |
| { | |
| return JsonConvert.DeserializeObject(response.Content, type, settings); | |
| } | |
| public T Deserialize<T>(RestResponseBase response) | |
| { | |
| return JsonConvert.DeserializeObject<T>(response.Content, settings); | |
| } | |
| public dynamic DeserializeDynamic(RestResponseBase response) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| #endregion | |
| #region ISerializer Members | |
| public virtual string Serialize(object instance, Type type) | |
| { | |
| return JsonConvert.SerializeObject(instance, Formatting.None/*, settings*/); | |
| } | |
| public virtual string ContentType | |
| { | |
| get { return "application/json"; } | |
| } | |
| public virtual Encoding ContentEncoding | |
| { | |
| get { return Encoding.UTF8; } | |
| } | |
| #endregion | |
| } | |
| public class CustomContractResolver : CamelCasePropertyNamesContractResolver | |
| { | |
| protected override string ResolvePropertyName(string propertyName) | |
| { | |
| return ToUnderscores(propertyName); | |
| } | |
| static string ToUnderscores(string str) | |
| { | |
| return Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + "_" + char.ToLower(m.Value[1])); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment