From ed14c56712fb0d6a2c432c1b408faac3e9403c17 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 28 Jun 2020 20:11:08 +0100 Subject: [PATCH] feat: Add verb to list recent timers --- Program.cs | 32 +++++++++++++++++++++++++---- Toggl/Query.cs | 56 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/Program.cs b/Program.cs index 6025dbb..dbfc7a3 100644 --- a/Program.cs +++ b/Program.cs @@ -23,6 +23,11 @@ public class ProjectsOptions : Options { } + [Verb("recent", HelpText = "Shows details of recent timers.")] + public class RecentOptions : Options + { + } + [Verb("current", HelpText = "Shows details of the current timer.")] public class CurrentOptions : Options { @@ -64,8 +69,9 @@ static void Main(string[] args) Console.OutputEncoding = Encoding.UTF8; try { - Parser.Default.ParseArguments(args) + Parser.Default.ParseArguments(args) .WithParsed(options => Projects(options).Wait()) + .WithParsed(options => Recent(options).Wait()) .WithParsed(options => Current(options).Wait()) .WithParsed(options => Set(options).Wait()) .WithParsed(options => Start(options).Wait()) @@ -100,6 +106,17 @@ static async Task GetMatchingProject(Query query, string projectNameOrI return project; } + static async Task FormatTimer(Query query, TimeEntry timer) + { + var project = await query.GetProject(timer.pid); + if (timer.stop.Year == 1) + { + var duration = TimeSpan.FromSeconds(DateTimeOffset.Now.ToUnixTimeSeconds() + timer.duration); + return $"{timer.start.ToString("yyyy-MM-dd HH:mm")}-now ({duration.ToString("hh\\:mm")}) - {project.name} - {timer.description} [{(timer.tags == null ? "" : string.Join(", ", timer.tags))}]"; + } + return $"{timer.start.ToString("yyyy-MM-dd HH:mm")}-{timer.stop.TimeOfDay.ToString("hh\\:mm")} ({(timer.stop - timer.start).ToString("hh\\:mm")}) - {project.name} - {timer.description} [{(timer.tags == null ? "" : string.Join(", ", timer.tags))}]"; + } + static async Task Projects(ProjectsOptions options) { var query = GetQuery(options); @@ -113,6 +130,15 @@ static async Task Projects(ProjectsOptions options) } } + static async Task Recent(Options options) + { + var query = GetQuery(options); + foreach (var timer in await query.GetRecentTimers()) + { + Console.WriteLine(await FormatTimer(query, timer)); + } + } + static async Task Current(Options options) { var query = GetQuery(options); @@ -123,9 +149,7 @@ static async Task Current(Options options) } else { - var project = await query.GetProject(timer.pid); - var duration = TimeSpan.FromSeconds(DateTimeOffset.Now.ToUnixTimeSeconds() + timer.duration); - Console.WriteLine($"\u25B6\uFE0F {duration} - {project.name} - {timer.description} [{(timer.tags == null ? "" : string.Join(", ", timer.tags))}]"); + Console.WriteLine($"\u25B6\uFE0F {await FormatTimer(query, timer)}"); } } diff --git a/Toggl/Query.cs b/Toggl/Query.cs index 58ec59f..fc83675 100644 --- a/Toggl/Query.cs +++ b/Toggl/Query.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -25,6 +27,7 @@ public TogglException(string message, Exception inner) : base(message, inner) readonly string Token; HttpClient Client = new HttpClient(); + Dictionary ProjectCache = new Dictionary(); public Query(string token) { @@ -35,21 +38,30 @@ internal async Task Send(HttpMethod method, string type, HttpContent con { var uri = new Uri(Endpoint + type); - var request = new HttpRequestMessage(method, uri); - request.Headers.UserAgent.Clear(); - request.Headers.UserAgent.Add(ProductInfoHeaderValue.Parse(UserAgent)); - request.Headers.Authorization = new AuthenticationHeaderValue("basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Token}:api_token"))); - request.Content = content; - - var response = await Client.SendAsync(request); - var text = await response.Content.ReadAsStringAsync(); - try - { - return JToken.Parse(text); - } - catch (JsonReaderException error) + while (true) { - throw new TogglException(text, error); + var request = new HttpRequestMessage(method, uri); + request.Headers.UserAgent.Clear(); + request.Headers.UserAgent.Add(ProductInfoHeaderValue.Parse(UserAgent)); + request.Headers.Authorization = new AuthenticationHeaderValue("basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Token}:api_token"))); + request.Content = content; + + var response = await Client.SendAsync(request); + if (response.StatusCode == HttpStatusCode.TooManyRequests) + { + Thread.Sleep(1000); + continue; + } + + var text = await response.Content.ReadAsStringAsync(); + try + { + return JToken.Parse(text); + } + catch (JsonReaderException error) + { + throw new TogglException(text, error); + } } } @@ -68,6 +80,15 @@ internal async Task Put(string type, JObject content) return await Send(HttpMethod.Put, type, new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json")); } + internal async Task GetCached(Dictionary cache, T key, Func> generator) + { + if (!cache.ContainsKey(key)) + { + cache[key] = await generator(); + } + return cache[key]; + } + public async Task> GetWorkspaces() { return (await Get("workspaces")).ToObject>(); @@ -88,7 +109,12 @@ public async Task> GetProjects(Workspace workspace) public async Task GetProject(int projectId) { - return (await Get($"projects/{projectId}"))["data"].ToObject(); + return await GetCached(ProjectCache, projectId, async () => (await Get($"projects/{projectId}"))["data"].ToObject()); + } + + public async Task> GetRecentTimers() + { + return (await Get("time_entries")).ToObject>(); } public async Task GetCurrentTimer()