Skip to content

Commit

Permalink
Fixed issues with idle checking
Browse files Browse the repository at this point in the history
  • Loading branch information
danexrc committed Oct 21, 2024
1 parent fb8a175 commit 864d1ba
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 67 deletions.
89 changes: 47 additions & 42 deletions Crackboard-VS/Crackboard_VSPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ public sealed class CrackboardPackage : AsyncPackage, IVsRunningDocTableEvents3
public const string PackageGuidString = "2817f103-f558-4681-b1be-19759908d9dd";
private static readonly string Endpoint = "http://crackboard.dev/heartbeat";
private DTE _dte;
private Timer _typingTimer;
private DateTime _lastHeartbeatTime;
private DateTime _lastActivityTime;
private string _sessionKey;
private RunningDocumentTable _runningDocTable;
private uint _rdtCookie;
private Timer _idleTimer;
private const int HEARTBEAT_INTERVAL = 2 * 60 * 1000;

protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
Expand All @@ -38,7 +40,6 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
_dte = (DTE)await GetServiceAsync(typeof(DTE));
if (_dte == null) return;

// Load session key from the options page settings
_sessionKey = GetSessionKey();

if (string.IsNullOrEmpty(_sessionKey))
Expand All @@ -51,6 +52,10 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke

var textEditorEvents = _dte.Events.TextEditorEvents;
textEditorEvents.LineChanged += OnLineChanged;

_lastActivityTime = DateTime.UtcNow;
_lastHeartbeatTime = DateTime.UtcNow;
_idleTimer = new Timer(CheckIdleAndSendHeartbeat, null, HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL);
}

protected override void Dispose(bool disposing)
Expand All @@ -61,13 +66,15 @@ protected override void Dispose(bool disposing)
_rdtCookie = 0;
}

_idleTimer?.Dispose();

base.Dispose(disposing);
}

// Event handler for document save
public int OnBeforeSave(uint docCookie)
{
ThreadHelper.ThrowIfNotOnUIThread();
_lastActivityTime = DateTime.UtcNow;
var document = FindDocumentByCookie(docCookie);
if (document != null)
{
Expand All @@ -85,7 +92,6 @@ public int OnBeforeSave(uint docCookie)
return VSConstants.S_OK;
}


public int OnAfterSave(uint docCookie) => VSConstants.S_OK;
public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK;
public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) => VSConstants.S_OK;
Expand All @@ -94,45 +100,49 @@ public int OnBeforeSave(uint docCookie)
public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) => VSConstants.S_OK;
public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK;


private void OnLineChanged(TextPoint startPoint, TextPoint endPoint, int hint)
{
if (_typingTimer == null)
ThreadHelper.ThrowIfNotOnUIThread();
_lastActivityTime = DateTime.UtcNow;

string vsLanguage = _dte.ActiveDocument?.Language;
string mappedLanguage = ConvertLanguageMapping(vsLanguage);

if (!string.IsNullOrEmpty(mappedLanguage))
{
_lastHeartbeatTime = DateTime.UtcNow;
var now = DateTime.UtcNow;
var timeSinceLastHeartbeat = now - _lastHeartbeatTime;

_typingTimer = new Timer(_ =>
if (timeSinceLastHeartbeat.TotalMilliseconds >= HEARTBEAT_INTERVAL)
{
Task.Run(async () =>
{
try
{
await JoinableTaskFactory.SwitchToMainThreadAsync(DisposalToken);

// Check if 2 minutes have passed since the last heartbeat

if (DateTime.UtcNow.Subtract(_lastHeartbeatTime).TotalMinutes >= 2)
{
string vsLanguage = _dte.ActiveDocument?.Language;
string mappedLanguage = ConvertLanguageMapping(vsLanguage);
if (!string.IsNullOrEmpty(mappedLanguage))
{
await SendHeartbeatAsync(mappedLanguage);
_lastHeartbeatTime = DateTime.UtcNow; // Update the last heartbeat time
}
}
}
catch (Exception ex)
{
// Handle exceptions safely
Console.WriteLine($"Error in Timer callback: {ex.Message}");
}
});
}, null, TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(2)); // Check every 2 minutes
_ = SendHeartbeatAsync(mappedLanguage);
}
}
}

// Finds currently open document by cookie
private void CheckIdleAndSendHeartbeat(object state)
{
JoinableTaskFactory.RunAsync(async () =>
{
await JoinableTaskFactory.SwitchToMainThreadAsync();

var now = DateTime.UtcNow;
var timeSinceLastActivity = now - _lastActivityTime;
var timeSinceLastHeartbeat = now - _lastHeartbeatTime;

if (timeSinceLastActivity.TotalMilliseconds < HEARTBEAT_INTERVAL &&
timeSinceLastHeartbeat.TotalMilliseconds >= HEARTBEAT_INTERVAL)
{
string vsLanguage = _dte.ActiveDocument?.Language;
string mappedLanguage = ConvertLanguageMapping(vsLanguage);
if (!string.IsNullOrEmpty(mappedLanguage))
{
await SendHeartbeatAsync(mappedLanguage);
}
}
});
}

private Document FindDocumentByCookie(uint docCookie)
{
ThreadHelper.ThrowIfNotOnUIThread();
Expand All @@ -144,7 +154,6 @@ private Document FindDocumentByCookie(uint docCookie)
});
}

// Sends a heartbeat to the Crackboard endpoint
private async Task SendHeartbeatAsync(string language)
{
if (string.IsNullOrEmpty(_sessionKey))
Expand All @@ -169,7 +178,7 @@ private async Task SendHeartbeatAsync(string language)
var response = await client.PostAsync(Endpoint, content);
if (response.IsSuccessStatusCode)
{
_lastHeartbeatTime = DateTime.Now;
_lastHeartbeatTime = DateTime.UtcNow;
Console.WriteLine("Heartbeat sent successfully.");
}
else
Expand All @@ -184,14 +193,12 @@ private async Task SendHeartbeatAsync(string language)
}
}

// Gets the session key from the options page settings
private string GetSessionKey()
{
OptionsPageGrid optionsPage = (OptionsPageGrid)GetDialogPage(typeof(OptionsPageGrid));
return optionsPage.SessionKey;
}

// Dictionary to map common Visual Studio language names to Crackboard (VS Code) language names
private static readonly Dictionary<string, string> LanguageMapDictionary = new Dictionary<string, string>
{
{ "CSharp", "csharp" },
Expand All @@ -208,8 +215,6 @@ private string GetSessionKey()
{ "Java", "java" },
};

// Converts Visual Studio language names to Crackboard (VS Code) language names
// Returns current language as lowercase if no mapping is found
private string ConvertLanguageMapping(string vsLanguage)
{
if (LanguageMapDictionary.TryGetValue(vsLanguage, out string mappedLanguage))
Expand All @@ -219,4 +224,4 @@ private string ConvertLanguageMapping(string vsLanguage)
return vsLanguage.ToLower();
}
}
}
}
50 changes: 25 additions & 25 deletions Crackboard-VS/source.extension.vsixmanifest
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Id="Crackboard_VS.2817f103-f558-4681-b1be-19759908d9d2" Version="1.1" Language="en-US" Publisher="danexrc" />
<DisplayName> Crackboard (VS)</DisplayName>
<Description xml:space="preserve">An (unofficial) extension to interface with crackboard.dev for Visual Studio.</Description>
<MoreInfo>https://github.com/danexrc/Crackboard-VS</MoreInfo>
<License>LICENSE.txt</License>
<ReleaseNotes>Fixed null reference exception on load when no active document is selected.</ReleaseNotes>
<Icon>icon.png</Icon>
<PreviewImage>crackboard.png</PreviewImage>
<Tags>C#. csharp, cpp, extension, development, crackboard</Tags>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[17.0, 18.0)">
<ProductArchitecture>amd64</ProductArchitecture>
</InstallationTarget>
</Installation>
<Dependencies>
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
</Dependencies>
<Prerequisites>
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[17.0,18.0)" DisplayName="Visual Studio core editor" />
</Prerequisites>
<Assets>
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
</Assets>
<Metadata>
<Identity Id="Crackboard_VS.2817f103-f558-4681-b1be-19759908d9d2" Version="1.2" Language="en-US" Publisher="danexrc" />
<DisplayName> Crackboard (VS)</DisplayName>
<Description xml:space="preserve">An (unofficial) extension to interface with crackboard.dev for Visual Studio.</Description>
<MoreInfo>https://github.com/danexrc/Crackboard-VS</MoreInfo>
<License>LICENSE.txt</License>
<ReleaseNotes>Fixed issues with idle checking.</ReleaseNotes>
<Icon>icon.png</Icon>
<PreviewImage>crackboard.png</PreviewImage>
<Tags>C#. csharp, cpp, extension, development, crackboard</Tags>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[17.0, 18.0)">
<ProductArchitecture>amd64</ProductArchitecture>
</InstallationTarget>
</Installation>
<Dependencies>
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
</Dependencies>
<Prerequisites>
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[17.0,18.0)" DisplayName="Visual Studio core editor" />
</Prerequisites>
<Assets>
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
</Assets>
</PackageManifest>

0 comments on commit 864d1ba

Please sign in to comment.