diff --git a/ActivityWatch.API/ActivityWatch.API.csproj b/ActivityWatch.API/ActivityWatch.API.csproj index fc625a7..70272f0 100644 --- a/ActivityWatch.API/ActivityWatch.API.csproj +++ b/ActivityWatch.API/ActivityWatch.API.csproj @@ -12,7 +12,6 @@ v4.7.2 512 true - true @@ -33,18 +32,16 @@ 4 - true + false - Key.snk + + false - - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - @@ -65,10 +62,12 @@ - - - + + + 13.0.1 + + \ No newline at end of file diff --git a/ActivityWatch.API/Key.snk b/ActivityWatch.API/Key.snk deleted file mode 100644 index 4e37624..0000000 Binary files a/ActivityWatch.API/Key.snk and /dev/null differ diff --git a/ActivityWatch.API/Properties/AssemblyInfo.cs b/ActivityWatch.API/Properties/AssemblyInfo.cs index a5f7502..cfb185a 100644 --- a/ActivityWatch.API/Properties/AssemblyInfo.cs +++ b/ActivityWatch.API/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.1.0.0")] -[assembly: AssemblyFileVersion("1.1.0.0")] +[assembly: AssemblyVersion("1.9.0")] +[assembly: AssemblyFileVersion("1.9.0")] \ No newline at end of file diff --git a/ActivityWatch.API/packages.config b/ActivityWatch.API/packages.config deleted file mode 100644 index 35adc25..0000000 --- a/ActivityWatch.API/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/ActivityWatchVS/AWPackage.cs b/ActivityWatchVS/AWPackage.cs index 275cda8..9543520 100644 --- a/ActivityWatchVS/AWPackage.cs +++ b/ActivityWatchVS/AWPackage.cs @@ -82,52 +82,55 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke { _progress = progress; _progress.Report(new ServiceProgressData("ActivityWatchVS starting")); - this.DisposalToken.Register(() => shutdown()); + this.DisposalToken.Register(() => this.Dispose()); // background thread - await BackgroundThreadInitialization(); + await JoinableTaskFactory.StartOnIdle(() => BackgroundThreadInitializationAsync(), VsTaskRunContext.UIThreadBackgroundPriority); + // main thread - await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - await MainThreadInitialization(); + await MainThreadInitializationAsync(); _isReady = true; _progress.Report(new ServiceProgressData("ActivityWatchVS running")); } - private async Task BackgroundThreadInitialization() + private async Task BackgroundThreadInitializationAsync() { - // Logger - _logService = new Services.LogService(this, await GetServiceAsync(typeof(SVsGeneralOutputWindowPane)) as IVsOutputWindowPane); - try { // ... Services VS _dte2Service = await GetServiceAsync(typeof(DTE)) as DTE2; // ... our Services - _awBinaryService = new Services.AwBinaryService(this); - _eventService = new Services.EventService(this); + _awBinaryService = new AwBinaryService(this); + _eventService = new EventService(this); // we are ready to send events // ... Listeners - _dte2EventListener = new Listeners.DTE2EventListener(this); + _dte2EventListener = new DTE2EventListener(this); } catch (Exception ex) { - _logService.Log(ex, "ActivityWatchVS: This is a bug, please report it!", activate: true); + _logService?.Log(ex, "ActivityWatchVS: This is a bug, please report it!"); } } - private async Task MainThreadInitialization() + private async Task MainThreadInitializationAsync() { - try { + await JoinableTaskFactory.SwitchToMainThreadAsync(DisposalToken); + + // Logger + _logService = new LogService(this, await GetServiceAsync(typeof(SVsGeneralOutputWindowPane)) as IVsOutputWindowPane); + + try + { // single class for AW ini file and our own settings - _awOptions = await Services.AWOptionService.InitializeAsync(this); + _awOptions = Services.AWOptionService.Initialize(this); } catch (Exception ex) { - _logService.Log(ex, "ActivityWatchVS: This is a bug, please report it!", activate: true); + _logService.Log(ex, "ActivityWatchVS: This is a bug, please report it!"); } } diff --git a/ActivityWatchVS/ActivityWatchVS.csproj b/ActivityWatchVS/ActivityWatchVS.csproj index 1d28b99..81a229f 100644 --- a/ActivityWatchVS/ActivityWatchVS.csproj +++ b/ActivityWatchVS/ActivityWatchVS.csproj @@ -1,19 +1,12 @@  - 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - true - - true - - - Key.snk + false AWPackage.ico @@ -98,9 +91,6 @@ GeneralSettings.Designer.cs - - Designer - Designer @@ -112,7 +102,6 @@ Menus.ctmenu - Always @@ -130,160 +119,32 @@ - - False - - - False - - - False - - - False - - - False - Binaries\IniWrapper.dll - - - ..\packages\Microsoft.ApplicationInsights.2.0.1\lib\net46\Microsoft.ApplicationInsights.dll - True - - - - False - ..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.Client.dll - - - False - ..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.Controls.dll - - - False - ..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.VersionControl.Client.dll - - - False - ..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.VersionControl.Common.dll - - - False - ..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.VersionControl.Controls.Common.dll - - - False - - - ..\packages\Microsoft.VisualStudio.CoreUtility.15.0.26228\lib\net45\Microsoft.VisualStudio.CoreUtility.dll - True - - - ..\packages\Microsoft.VisualStudio.Imaging.15.0.26228\lib\net45\Microsoft.VisualStudio.Imaging.dll - True - - - ..\packages\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.14.3.25408\lib\net20\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll - True - - - ..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6071\lib\Microsoft.VisualStudio.OLE.Interop.dll - - - ..\packages\Microsoft.VisualStudio.Shell.15.0.15.0.26228\lib\Microsoft.VisualStudio.Shell.15.0.dll - True - - - ..\packages\Microsoft.VisualStudio.Shell.Framework.15.0.26228\lib\net45\Microsoft.VisualStudio.Shell.Framework.dll - True - - - ..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll - True - - - ..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll - True - - - ..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll - True - - - ..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll - True - - - ..\packages\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll - True - - - ..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll - True - - - ..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll - True - - - False - ..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.VisualStudio.TeamFoundation.dll - - - ..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.VisualStudio.TeamFoundation.VersionControl.dll - - - ..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll - True - - - ..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll - True - - - ..\packages\Microsoft.VisualStudio.Threading.15.0.240\lib\net45\Microsoft.VisualStudio.Threading.dll - True - - - ..\packages\Microsoft.VisualStudio.Utilities.15.0.26228\lib\net46\Microsoft.VisualStudio.Utilities.dll - True - - - ..\packages\Microsoft.VisualStudio.Validation.15.0.82\lib\net45\Microsoft.VisualStudio.Validation.dll - True - + + 4.1.0 + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + - - False - - - ..\packages\System.IO.Abstractions.2.1.0.178\lib\net40\System.IO.Abstractions.dll - True - - - ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll - True - True - - - ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll - - - - - - PublicResXFileCodeGenerator @@ -306,30 +167,6 @@ MSBuild:Compile - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - \ No newline at end of file diff --git a/ActivityWatchVS/Binaries/IniWrapper.dll b/ActivityWatchVS/Binaries/IniWrapper.dll deleted file mode 100644 index c00bdad..0000000 Binary files a/ActivityWatchVS/Binaries/IniWrapper.dll and /dev/null differ diff --git a/ActivityWatchVS/CHANGELOG.md b/ActivityWatchVS/CHANGELOG.md index f4acd09..fa84b51 100644 --- a/ActivityWatchVS/CHANGELOG.md +++ b/ActivityWatchVS/CHANGELOG.md @@ -17,6 +17,10 @@ or on vsixgallery.com (beta/alpha/testing) - [ ] TFS / Work Item tracking - [ ] ... your ideas +## 1.9.0 (testing) Visual Studio 2022 support + +- [x] thanks to https://github.com/DarkOoze we have VS2022 Support in testing now. + ## 1.1.x (stable) get versioning right - [x] fix appveyor/vsixmanifest files for correct version numbers everywhere https://github.com/LaggAt/ActivityWatchVS/issues/2 diff --git a/ActivityWatchVS/Listeners/DTE2EventListener.cs b/ActivityWatchVS/Listeners/DTE2EventListener.cs index 5334158..4973d64 100644 --- a/ActivityWatchVS/Listeners/DTE2EventListener.cs +++ b/ActivityWatchVS/Listeners/DTE2EventListener.cs @@ -1,4 +1,7 @@ using EnvDTE; + +using Microsoft.VisualStudio.Shell; + using System; using System.IO; using System.Runtime.CompilerServices; @@ -22,6 +25,8 @@ internal sealed class DTE2EventListener : IDisposable public DTE2EventListener(AWPackage package) { + ThreadHelper.ThrowIfNotOnUIThread(); + this._package = package; var dte2Events = this._package.DTE2Service.Events; @@ -183,6 +188,8 @@ private void solutionEvents_Opened() public void Dispose() { + ThreadHelper.ThrowIfNotOnUIThread(); + _buildEv.OnBuildBegin -= buildEvents_OnBuildBegin; _buildEv.OnBuildDone -= buildEvents_OnBuildDone; _documentEv.DocumentOpened -= documentEv_DocumentOpened; diff --git a/ActivityWatchVS/Properties/AssemblyInfo.cs b/ActivityWatchVS/Properties/AssemblyInfo.cs index 1e11a52..6027cdd 100644 --- a/ActivityWatchVS/Properties/AssemblyInfo.cs +++ b/ActivityWatchVS/Properties/AssemblyInfo.cs @@ -22,5 +22,5 @@ // // You can specify all the values or you can default the Build and Revision Numbers by using the '*' // as shown below: [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.1.*")] -[assembly: AssemblyFileVersion("1.1.*")] \ No newline at end of file +[assembly: AssemblyVersion("1.9.0")] +[assembly: AssemblyFileVersion("1.9.0")] \ No newline at end of file diff --git a/ActivityWatchVS/Services/AWOptionService.cs b/ActivityWatchVS/Services/AWOptionService.cs index 0a912c5..782a6ed 100644 --- a/ActivityWatchVS/Services/AWOptionService.cs +++ b/ActivityWatchVS/Services/AWOptionService.cs @@ -70,7 +70,7 @@ public bool IsProductive #region Methods - internal static async Task InitializeAsync(AWPackage package) + internal static AWOptionService Initialize(AWPackage package) { return new AWOptionService(package); } diff --git a/ActivityWatchVS/Services/EventService.cs b/ActivityWatchVS/Services/EventService.cs index 848151c..5313bc5 100644 --- a/ActivityWatchVS/Services/EventService.cs +++ b/ActivityWatchVS/Services/EventService.cs @@ -1,5 +1,9 @@ using ActivityWatch.API.V1; using ActivityWatchVS.Tools; + +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -16,22 +20,21 @@ internal class EventService private const double AFK_SECONDS = 60 * 15; private const double MIN_HEARTBEAT_SECONDS = 60; - private const int THREAD_WAIT_TIMEOUT_SECONDS = 60; private const int WAIT_FOR_EVENT_SECONDS = 20; #endregion Constants #region Fields - private CancellationTokenSource _cancellationTokenSource; private Client _client; - private ManualResetEvent _continueLoopManualResetEvent = new ManualResetEvent(false); - private bool _doShutdown = false; - private ConcurrentQueue _events = new ConcurrentQueue(); private bool _isBucketSent = false; - private AWPackage _package; - private Task _thread = null; - private Dictionary bucketIdPartLastEvent = new Dictionary(); + private Task _currentTask = Task.CompletedTask; + + private readonly object _currentTaskLock = new object(); + private readonly AWPackage _package; + private readonly ConcurrentQueue _events = new ConcurrentQueue(); + private readonly Dictionary _bucketIdPartLastEvent = new Dictionary(); + private readonly Semaphore _semaphore = new Semaphore(1, 1, typeof(EventService).FullName); #endregion Fields @@ -42,10 +45,6 @@ internal EventService(AWPackage package) _package = package; } - private EventService() - { - } - #endregion Constructors #region Methods @@ -55,48 +54,46 @@ public void Reset() _isBucketSent = false; } - internal void AddEvent(ActivityWatch.API.V1.Event ev) + internal void AddEvent(Event ev) { - if (_doShutdown) //(_cancellationTokenSource?.IsCancellationRequested ?? false) - { + if (_package.DisposalToken.IsCancellationRequested) return; - } + _events.Enqueue(ev); - _continueLoopManualResetEvent.Set(); - startThread(); + + lock(_currentTaskLock) + { + if (_currentTask.IsCompleted) + { + _currentTask = this.ScheduleBackgroundTaskAsync(() => this.ProcessEventQueueAsync(_package.DisposalToken)); + } + } } internal void Shutdown() { - //_cancellationTokenSource?.Cancel(); - _doShutdown = true; - _continueLoopManualResetEvent.Set(); - - //if (_thread != null) - //{ - // Task.WaitAll(new Task[] { _thread }, THREAD_WAIT_TIMEOUT_SECONDS * 1000); - //} - _thread?.Wait(THREAD_WAIT_TIMEOUT_SECONDS * 1000); + _package.JoinableTaskFactory.Run(ProcessEventsAsync); } - private static string getBucketId(Event ev) + private static string GetBucketId(Event ev) { return $"{ev.Data.BucketIDCustomPart}_{Environment.MachineName}"; } - private bool mergeEventsAndOutFinishedEvent(out ActivityWatch.API.V1.Event logEvent) + private bool MergeEventsAndOutFinishedEvent(out Event logEvent) { logEvent = null; - if (_events.TryDequeue(out ActivityWatch.API.V1.Event newEvent)) + if (_events.TryDequeue(out Event newEvent)) { - _package.LogService.Log($"new Event for {getBucketId(newEvent)}: " + newEvent.ToJson(), LogService.EErrorLevel.Debug); + _package.LogService.Log($"new Event for {GetBucketId(newEvent)}: " + newEvent.ToJson(), LogService.EErrorLevel.Debug); + + Event oldEvent = null; - ActivityWatch.API.V1.Event oldEvent = null; // do we have an old event? - if (bucketIdPartLastEvent.ContainsKey(newEvent.Data.BucketIDCustomPart)) + if (_bucketIdPartLastEvent.ContainsKey(newEvent.Data.BucketIDCustomPart)) { // compare old to new event - oldEvent = bucketIdPartLastEvent[newEvent.Data.BucketIDCustomPart]; + oldEvent = _bucketIdPartLastEvent[newEvent.Data.BucketIDCustomPart]; var duration = (newEvent.Timestamp - oldEvent.Timestamp).TotalSeconds; oldEvent.Duration = duration; @@ -107,8 +104,8 @@ private bool mergeEventsAndOutFinishedEvent(out ActivityWatch.API.V1.Event logEv else if (!oldEvent.Equals(newEvent)) { // different event, log and keep the new one - bucketIdPartLastEvent.Remove(oldEvent.Data.BucketIDCustomPart); - bucketIdPartLastEvent[newEvent.Data.BucketIDCustomPart] = newEvent; + _bucketIdPartLastEvent.Remove(oldEvent.Data.BucketIDCustomPart); + _bucketIdPartLastEvent[newEvent.Data.BucketIDCustomPart] = newEvent; logEvent = oldEvent; return true; } @@ -116,25 +113,25 @@ private bool mergeEventsAndOutFinishedEvent(out ActivityWatch.API.V1.Event logEv { // same event, but older than minimum hearbeat interval different event, push // old event with new duration, and save old - bucketIdPartLastEvent[newEvent.Data.BucketIDCustomPart] = newEvent; + _bucketIdPartLastEvent[newEvent.Data.BucketIDCustomPart] = newEvent; } } else { // no old event, keep the record - bucketIdPartLastEvent[newEvent.Data.BucketIDCustomPart] = newEvent; + _bucketIdPartLastEvent[newEvent.Data.BucketIDCustomPart] = newEvent; } } else { - if (_doShutdown) //(_cancellationTokenSource.IsCancellationRequested) + if (_package.DisposalToken.IsCancellationRequested) { // we are shutting down, create stop events - var stopEvent = bucketIdPartLastEvent.Values.FirstOrDefault(); + var stopEvent = _bucketIdPartLastEvent.Values.FirstOrDefault(); if (stopEvent != null) { // write event, we are about to stop - bucketIdPartLastEvent.Remove(stopEvent.Data.BucketIDCustomPart); + _bucketIdPartLastEvent.Remove(stopEvent.Data.BucketIDCustomPart); stopEvent.Duration = (DateTimeOffset.UtcNow - stopEvent.Timestamp).TotalSeconds; logEvent = stopEvent; return true; @@ -143,11 +140,11 @@ private bool mergeEventsAndOutFinishedEvent(out ActivityWatch.API.V1.Event logEv else { // nothing to dequeue, create stop events (AFK) - var stopEvent = bucketIdPartLastEvent.Values.FirstOrDefault(e => e.Timestamp.AddSeconds((int)e.Duration) < DateTimeOffset.UtcNow.AddSeconds(-AFK_SECONDS)); + var stopEvent = _bucketIdPartLastEvent.Values.FirstOrDefault(e => e.Timestamp.AddSeconds((int)e.Duration) < DateTimeOffset.UtcNow.AddSeconds(-AFK_SECONDS)); if (stopEvent != null) { // write event, user is afk - bucketIdPartLastEvent.Remove(stopEvent.Data.BucketIDCustomPart); + _bucketIdPartLastEvent.Remove(stopEvent.Data.BucketIDCustomPart); stopEvent.Duration = AFK_SECONDS; logEvent = stopEvent; return true; @@ -157,23 +154,25 @@ private bool mergeEventsAndOutFinishedEvent(out ActivityWatch.API.V1.Event logEv return false; } - private async Task postBucketIfNeededAsync(string bucket_id, string bucketType, CancellationToken cancellationToken) + private async Task PostCreateBucketAsync(string bucket_id, string bucketType, CancellationToken cancellationToken) { if (!_isBucketSent || _package.AwOptions.ActivityWatchBaseURL != _client?.BaseUrl) { - _client = new ActivityWatch.API.V1.Client(); + _client = new Client(); _client.BaseUrl = _package.AwOptions.ActivityWatchBaseURL; - var awBucket = new ActivityWatch.API.V1.CreateBucket() + + var awBucket = new CreateBucket() { Client = AWPackage.NAME_ACTIVITY_WATCHER, Hostname = Environment.MachineName, Type = bucketType }; + try { var bucketResult = await _client.BucketsPostAsync(awBucket, bucket_id, cancellationToken).ConfigureAwait(false); } - catch (ActivityWatch.API.V1.AWApiException ex) + catch (AWApiException ex) { if (ex.StatusCode == 304) { @@ -184,85 +183,100 @@ private async Task postBucketIfNeededAsync(string bucket_id, string bucketType, throw; } } - _isBucketSent = true; + + return true; } + + return false; } - private void pushEventsThread(CancellationToken cancellationToken) + private async Task PushEventAsync(Event logEvent, CancellationToken cancellationToken) { - for (; ; ) + cancellationToken.ThrowIfCancellationRequested(); + + await TaskScheduler.Default; + + try { - try + var bucket_id = GetBucketId(logEvent); + + bool ok = false; + do { - bool hasFired = _continueLoopManualResetEvent.WaitOne(WAIT_FOR_EVENT_SECONDS * 1000); - while (mergeEventsAndOutFinishedEvent(out ActivityWatch.API.V1.Event logEvent)) + try { - var bucket_id = getBucketId(logEvent); + if (!_isBucketSent) + { + _isBucketSent = await PostCreateBucketAsync(bucket_id, logEvent.Data.TypeName, cancellationToken); + } - bool ok = false; - do + await _client.BucketsIdEventsPostAsync(logEvent, bucket_id, cancellationToken); + + _package.LogService.Log($"Sent event for {bucket_id}: " + logEvent.ToJson(), LogService.EErrorLevel.Debug); + ok = true; + } + catch (Exception ex) + { + var sockEx = ex.GetInnerst(); + if (sockEx != null) { - try - { - //await - postBucketIfNeededAsync(bucket_id, logEvent.Data.TypeName, _cancellationTokenSource.Token) - .GetAwaiter().GetResult(); - //await - _client.BucketsIdEventsPostAsync(logEvent, bucket_id, _cancellationTokenSource.Token) - .GetAwaiter().GetResult(); - _package.LogService.Log($"Sent event for {bucket_id}: " + logEvent.ToJson(), LogService.EErrorLevel.Debug); - ok = true; - } - catch (Exception ex) + if (sockEx.SocketErrorCode == SocketError.ConnectionRefused) { - var sockEx = ex.GetInnerst(); - if (sockEx != null) - { - if(sockEx.SocketErrorCode == SocketError.ConnectionRefused) - { - //aw_service is not running, try to find and start it - _package.AwBinaryService.TryStartAwServer(); - } - } - - // some error, retry regularly - _package.LogService.Log(ex, logEvent.ToJson()); - if (!_doShutdown) //(!cancellationToken.IsCancellationRequested) - { - Reset(); - // don't ddos - Thread.Sleep(5000); - } + //aw_service is not running, try to find and start it + _package.AwBinaryService.TryStartAwServer(); } - } while (!ok && !_doShutdown); //cancellationToken.IsCancellationRequested); // don't retry if we do shut down - } - _continueLoopManualResetEvent.Reset(); - if (_doShutdown && _events.IsEmpty) //_events.IsEmpty needed? - { - break; + } + + // some error, retry regularly + _package.LogService.Log(ex, logEvent.ToJson()); + Reset(); + // don't ddos + await Task.Delay(5000); } - } - catch (Exception ex) - { - _package.LogService.Log(ex); - } + } while (!ok); + } + catch (Exception ex) + { + _package.LogService.Log(ex); + } + } + + private async Task ScheduleBackgroundTaskAsync(Func action) + { + try + { + await _package.JoinableTaskFactory.StartOnIdle(action); + } + catch (Exception ex) + { + _package.LogService.Log(ex); } - _package.LogService.Log("EventService loop ended", LogService.EErrorLevel.Debug); } - private void startThread() + private Task ProcessEventsAsync() => ProcessEventQueueAsync(CancellationToken.None); + + private async Task ProcessEventQueueAsync(CancellationToken token) { - if (_thread == null) + await TaskScheduler.Default; + + if (!_semaphore.WaitOne(WAIT_FOR_EVENT_SECONDS * 1000)) + { + return; + } + + try { - lock (this) + while (MergeEventsAndOutFinishedEvent(out Event logEvent)) { - if (_thread == null) - { - _cancellationTokenSource = new CancellationTokenSource(); - _thread = Task.Run(() => pushEventsThread(_cancellationTokenSource.Token)); - } + await PushEventAsync(logEvent, token); } } + finally + { + _semaphore.Release(); + } + + _package.LogService.Log("EventService loop ended", LogService.EErrorLevel.Debug); } #endregion Methods diff --git a/ActivityWatchVS/Services/LogService.cs b/ActivityWatchVS/Services/LogService.cs index a28b57b..b88f507 100644 --- a/ActivityWatchVS/Services/LogService.cs +++ b/ActivityWatchVS/Services/LogService.cs @@ -1,5 +1,7 @@ -using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; using System; +using System.Collections.Concurrent; using System.Diagnostics; using System.Reflection; @@ -9,8 +11,10 @@ internal class LogService { #region Fields - private AWPackage _package; - private IVsOutputWindowPane _vsOutputWindowPane; + private readonly AWPackage _package; + private readonly IVsOutputWindowPane _vsOutputWindowPane; + private readonly ConcurrentQueue _logQueue; + private volatile bool _activate; #endregion Fields @@ -18,17 +22,19 @@ internal class LogService public LogService(AWPackage package, IVsOutputWindowPane vsOutputWindowPane) { + ThreadHelper.ThrowIfNotOnUIThread(); + + _logQueue = new ConcurrentQueue(); _package = package; _vsOutputWindowPane = vsOutputWindowPane; _vsOutputWindowPane.SetName(AWPackage.NAME_CS_PLUGIN); - bool activate = false; #if DEBUG LogLevel = EErrorLevel.Debug; - activate = true; + _activate = true; #endif // say hello - Log(AWPackage.NAME_CS_PLUGIN + " " + getCurrentVersion() + " running", EErrorLevel.Info, activate); + Log(AWPackage.NAME_CS_PLUGIN + " " + getCurrentVersion() + " running", EErrorLevel.Info); } #endregion Constructors @@ -61,16 +67,28 @@ internal void Log(string msg, EErrorLevel errorLevel = EErrorLevel.Debug, bool a { return; } - try + + _logQueue.Enqueue(msg + "\r\n"); + + _activate = _activate || activate; + + _ = _package.JoinableTaskFactory.StartOnIdle(FlushLogsToOutputWindowPane, VsTaskRunContext.UIThreadBackgroundPriority); + } + + private void FlushLogsToOutputWindowPane() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if(_activate) { - _vsOutputWindowPane.OutputString(msg + "\r\n"); - if (activate) - { - _vsOutputWindowPane.Activate(); - } + _vsOutputWindowPane.Activate(); + _activate = false; + } + + while (_logQueue.Count > 0 && _logQueue.TryDequeue(out string msg)) + { + _vsOutputWindowPane.OutputStringThreadSafe(msg); } - catch - { } } private string getCurrentVersion() diff --git a/ActivityWatchVS/app.config b/ActivityWatchVS/app.config index 03d1ab3..92819f4 100644 --- a/ActivityWatchVS/app.config +++ b/ActivityWatchVS/app.config @@ -9,7 +9,7 @@ - + diff --git a/ActivityWatchVS/packages.config b/ActivityWatchVS/packages.config deleted file mode 100644 index 49dfeed..0000000 --- a/ActivityWatchVS/packages.config +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ActivityWatchVS/source.extension.cs b/ActivityWatchVS/source.extension.cs index 0505b9c..69bfbf3 100644 --- a/ActivityWatchVS/source.extension.cs +++ b/ActivityWatchVS/source.extension.cs @@ -14,7 +14,7 @@ internal sealed partial class Vsix Activity Watch tracks windows titles, we extend this functionality from inside Visual Studio and track your file edits."; public const string Language = "en-US"; - public const string Version = "1.1.0"; + public const string Version = "1.9.0"; public const string Author = "Florian Lagg"; public const string Tags = "Activity Watch, quantified self, lifelog, coding, tracking"; } diff --git a/ActivityWatchVS/source.extension.vsixmanifest b/ActivityWatchVS/source.extension.vsixmanifest index 2c72e98..0602fad 100644 --- a/ActivityWatchVS/source.extension.vsixmanifest +++ b/ActivityWatchVS/source.extension.vsixmanifest @@ -1,32 +1,47 @@  - - - ActivityWatchVS - Track your work with this plugin and activitywatch.net. Ever wanted to know where you spend your time? + + + ActivityWatchVS + Track your work with this plugin and activitywatch.net. Ever wanted to know where you spend your time? The Plugin is a Watcher for Visual Studio. It enables tracking of all, you do in your solution. We send this data to an Activity Watch installation on your machine, all tracked data belongs to you. Activity Watch tracks windows titles, we extend this functionality from inside Visual Studio and track your file edits. - https://github.com/LaggAt/ActivityWatchVS/ - LICENSE.txt - GETTING_STARTED.txt - CHANGELOG.md - Resources\AWPackage.ico - Resources\Screenshots\VS-Options-Dialog.png - Activity Watch, quantified self, lifelog, coding, tracking - - - - - - - - - - - - - - - + https://github.com/LaggAt/ActivityWatchVS/ + LICENSE.txt + GETTING_STARTED.txt + CHANGELOG.md + Resources\AWPackage.ico + Resources\Screenshots\VS-Options-Dialog.png + Activity Watch, quantified self, lifelog, coding, tracking + + + + x86 + + + x86 + + + x86 + + + amd64 + + + amd64 + + + amd64 + + + + + + + + + + + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index a8d840a..3cba25b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -image: Visual Studio 2017 +image: Visual Studio 2022 branches: only: - stable @@ -6,7 +6,7 @@ branches: - alpha - testing -version: 1.1.{build} +version: 1.9.{build} install: - ps: (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex