Skip to content

Commit

Permalink
Fix rate limiting issue
Browse files Browse the repository at this point in the history
  • Loading branch information
eXpl0it3r committed Feb 8, 2024
1 parent 5b47ccd commit b2c5551
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 51 deletions.
2 changes: 1 addition & 1 deletion Clockify.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="6.0.5.128" />
<PackageReference Include="streamdeck-client-csharp" Version="4.3.0" />
<PackageReference Include="StreamDeck-Tools" Version="6.1.1" />
<PackageReference Include="StreamDeck-Tools" Version="6.2.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
81 changes: 51 additions & 30 deletions Clockify/ClockifyContext.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Clockify.Net;
using Clockify.Net.Models.Projects;
using Clockify.Net.Models.Tasks;
using Clockify.Net.Models.TimeEntries;
using Clockify.Net.Models.Users;
using Clockify.Net.Models.Workspaces;

namespace Clockify;

Expand All @@ -15,6 +17,7 @@ public class ClockifyContext

private ClockifyClient _clockifyClient;
private CurrentUserDto _currentUser = new();
private List<WorkspaceDto> _workspaces = new();

private string _apiKey = string.Empty;
private string _clientName = string.Empty;
Expand All @@ -34,13 +37,12 @@ public bool IsValid()
return _clockifyClient != null;
}

public async Task ToggleTimerAsync()
public async Task<bool> ToggleTimerAsync()
{
// TODO Validation for project
if (_clockifyClient is null || string.IsNullOrWhiteSpace(_workspaceName))
{
_logger.LogWarn($"Invalid settings for toggle {_workspaceName}, {_projectName}, {_timerName}");
return;
return false;
}

var runningTimer = await GetRunningTimerAsync();
Expand All @@ -49,17 +51,16 @@ public async Task ToggleTimerAsync()

if (runningTimer != null)
{
return;
_logger.LogDebug("Toggle successful, timer has been stopped");
return false;
}
var workspaces = await _clockifyClient.GetWorkspacesAsync();
if (!workspaces.IsSuccessful || workspaces.Data is null)

var workspace = _workspaces.SingleOrDefault(w => w.Name == _workspaceName);
if (workspace == null)
{
_logger.LogWarn("Unable to retrieve available workspaces");
return;
return false;
}

var workspace = workspaces.Data.Single(w => w.Name == _workspaceName);

var timeEntryRequest = new TimeEntryRequest
{
UserId = _currentUser.Id,
Expand All @@ -74,7 +75,8 @@ public async Task ToggleTimerAsync()

if (project is null)
{
return;
_logger.LogDebug("There's no match project");
return false;
}

timeEntryRequest.ProjectId = project.Id;
Expand All @@ -89,27 +91,31 @@ public async Task ToggleTimerAsync()
}
}

await _clockifyClient.CreateTimeEntryAsync(workspace.Id, timeEntryRequest);
var timeEntry = await _clockifyClient.CreateTimeEntryAsync(workspace.Id, timeEntryRequest);

if (!timeEntry.IsSuccessful || timeEntry.Data == null)
{
_logger.LogError($"TimeEntry creation failed: {timeEntry.ErrorMessage}");
}

_logger.LogInfo($"Toggle Timer {_workspaceName}, {_projectName}, {_taskName}, {_timerName}");
return true;
}

public async Task<TimeEntryDtoImpl> GetRunningTimerAsync()
{
// TODO Validation for project
if (_clockifyClient is null || string.IsNullOrWhiteSpace(_workspaceName))
{
_logger.LogWarn($"Invalid settings for running timer {_workspaceName}");
return null;
}

var workspaces = await _clockifyClient.GetWorkspacesAsync();
if (!workspaces.IsSuccessful || workspaces.Data is null)
var workspace = _workspaces.SingleOrDefault(w => w.Name == _workspaceName);
if (workspace == null)
{
_logger.LogWarn("Unable to retrieve available workspaces");
return null;
}

var workspace = workspaces.Data.Single(w => w.Name == _workspaceName);

var timeEntries = await _clockifyClient.FindAllTimeEntriesForUserAsync(workspace.Id, _currentUser.Id, inProgress: true);
if (!timeEntries.IsSuccessful || timeEntries.Data is null)
{
Expand Down Expand Up @@ -155,23 +161,40 @@ public async Task UpdateSettings(PluginSettings settings)
_apiKey = settings.ApiKey;

_clockifyClient = new ClockifyClient(_apiKey, settings.ServerUrl);

if (!await TestConnectionAsync())
{
_logger.LogWarn("Invalid server URL or API key");
_clockifyClient = null;
_currentUser = new CurrentUserDto();
return;
}

_logger.LogInfo("Connection to Clockify successfully established");
}

if (!_workspaces.Any() || settings.WorkspaceName != _workspaceName)
{
await ReloadCacheAsync();
}

_workspaceName = settings.WorkspaceName;
_projectName = settings.ProjectName;
_taskName = settings.TaskName;
_timerName = settings.TimerName;
_clientName = settings.ClientName;
}

if (await TestConnectionAsync())
private async Task ReloadCacheAsync()
{
var workspaces = await _clockifyClient.GetWorkspacesAsync();
if (!workspaces.IsSuccessful || workspaces.Data is null)
{
_logger.LogInfo("Connection to Clockify successfully established");
_logger.LogWarn($"Unable to retrieve available workspaces: {workspaces.ErrorMessage}");
return;
}

_logger.LogWarn("Invalid server URL or API key");
_clockifyClient = null;
_currentUser = new CurrentUserDto();
_workspaces = workspaces.Data;
}

private async Task StopRunningTimerAsync()
Expand All @@ -181,14 +204,12 @@ private async Task StopRunningTimerAsync()
return;
}

var workspaces = await _clockifyClient.GetWorkspacesAsync();
if (!workspaces.IsSuccessful || workspaces.Data is null)
var workspace = _workspaces.SingleOrDefault(w => w.Name == _workspaceName);
if (workspace == null)
{
_logger.LogWarn("Unable to retrieve available workspaces");
return;
}

var workspace = workspaces.Data.Single(w => w.Name == _workspaceName);

var runningTimer = await GetRunningTimerAsync();
if (runningTimer == null)
{
Expand All @@ -214,7 +235,7 @@ private async Task<ProjectDtoImpl> FindMatchingProjectAsync(string workspaceId)
var projects = await _clockifyClient.FindAllProjectsOnWorkspaceAsync(workspaceId, false, _projectName, pageSize: 5000);
if (!projects.IsSuccessful || projects.Data is null)
{
_logger.LogWarn($"Unable to retrieve project {_projectName} on workspace {_workspaceName}");
_logger.LogWarn($"Unable to retrieve project {_projectName} on workspace {_workspaceName}: {projects.ErrorMessage}");
return null;
}

Expand Down
58 changes: 40 additions & 18 deletions Clockify/ToggleAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ public class ToggleAction : KeypadBase
{
private const uint InactiveState = 0;
private const uint ActiveState = 1;

private readonly ClockifyContext _clockifyContext;

private readonly Logger _logger;
private readonly PluginSettings _settings;

private int _ticks = 10;
private DateTime? _lastStartDate;

public ToggleAction(ISDConnection connection, InitialPayload payload)
: base(connection, payload)
{
Expand All @@ -39,14 +43,12 @@ public override async void KeyReleased(KeyPayload payload)
{
_logger.LogDebug("Key Released");

if (_clockifyContext.IsValid())
{
await _clockifyContext.ToggleTimerAsync();
}
else
if (!_clockifyContext.IsValid() || !await _clockifyContext.ToggleTimerAsync())
{
await Connection.ShowAlert();
}

_ticks = 10;
}

public override async void OnTick()
Expand All @@ -57,21 +59,40 @@ public override async void OnTick()
return;
}

var timer = await _clockifyContext.GetRunningTimerAsync();
var timerTime = string.Empty;

if (timer?.TimeInterval.Start != null)
if (_ticks > 10)
{
var timeDifference = DateTime.UtcNow - timer.TimeInterval.Start.Value.UtcDateTime;
timerTime = $"{timeDifference.Hours:d2}:{timeDifference.Minutes:d2}:{timeDifference.Seconds:d2}";
await Connection.SetStateAsync(ActiveState);
var timer = await _clockifyContext.GetRunningTimerAsync();
var timerTime = string.Empty;

if (timer?.TimeInterval.Start != null)
{
var timeDifference = DateTime.UtcNow - timer.TimeInterval.Start.Value.UtcDateTime;
timerTime = $"{timeDifference.Hours:d2}:{timeDifference.Minutes:d2}:{timeDifference.Seconds:d2}";

await Connection.SetStateAsync(ActiveState);
_lastStartDate = timer.TimeInterval.Start.Value.UtcDateTime;
}
else
{
await Connection.SetStateAsync(InactiveState);
_lastStartDate = null;
}

await Connection.SetTitleAsync(CreateTimerText(timerTime));
_ticks = 0;
return;
}
else

if (_lastStartDate != null)
{
await Connection.SetStateAsync(InactiveState);
var timeDifference = DateTime.UtcNow - _lastStartDate.Value;
var timerTime = $"{timeDifference.Hours:d2}:{timeDifference.Minutes:d2}:{timeDifference.Seconds:d2}";

await Connection.SetStateAsync(ActiveState);
await Connection.SetTitleAsync(CreateTimerText(timerTime));
}

await Connection.SetTitleAsync(CreateTimerText(timerTime));
_ticks++;
}

public override async void ReceivedSettings(ReceivedSettingsPayload payload)
Expand All @@ -91,9 +112,10 @@ private string CreateTimerText(string timerTime)
if (!string.IsNullOrEmpty(_settings.TitleFormat))
{
return _settings.TitleFormat
.Replace("{projectName}", _settings.ProjectName)
.Replace("{taskName}", _settings.TaskName)
.Replace("{timerName}", _settings.TimerName)
.ToLower()
.Replace("{projectname}", _settings.ProjectName)
.Replace("{taskname}", _settings.TaskName)
.Replace("{timername}", _settings.TimerName)
.Replace("{timer}", timerTime);
}

Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,20 @@ https://user-images.githubusercontent.com/920861/132741561-6f9f3ff0-a920-408d-82

- Why am I getting a yellow triangle when pressing the button?
- Your API Key is likely incorrect
- If you have clients assigned to your project, make sure they're configured in the Stream Deck
- Why am I not seeing the running timer on my button?
- Make sure you haven't set a title, as this will override any other content
- Make sure the API Key, Workspace name and optional the project and timer name
- Why does the timer always start with a negative number?
- This can happen when your local computer time isn't in sync with the Clockify server time
- Make sure you synchronize your clock with a time server
- Why does it always take some seconds to show the timer running?
- Due to API rate limits, there's some magical caching going on, leading to certain delays
- Why can't I select my Workspace and Project in a dropdown menu?
- Because I was lazy 😅
- Where can I find the logs?
- Windows: `%appdata%\Elgato\StreamDeck\Plugins\dev.duerrenberger.clockify.sdPlugin\pluginlog.log`
- macOS: `~/Library/Application Support/com.elgato.StreamDeck/Plugins/dev.duerrenberger.clockify.sdPlugin/pluginlog.log`
- Windows: `%appdata%\Elgato\StreamDeck\Plugins\dev.duerrenberger.clockify.sdPlugin\Windows\pluginlog.log`
- macOS: `~/Library/Application Support/com.elgato.StreamDeck/Plugins/dev.duerrenberger.clockify.sdPlugin/macOS/pluginlog.log`
- IT DOESN'T WORK, WHY?!?
- Feel free to open a [GitHub issue](https://github.com/eXpl0it3r/streamdeck-clockify/issues) or ping me on [Twitter](https://twitter.com/DarkCisum)

Expand Down

0 comments on commit b2c5551

Please sign in to comment.