Skip to content

Commit

Permalink
Update DynamicSourceCache when executing specific commands
Browse files Browse the repository at this point in the history
  • Loading branch information
AnderssonPeter committed Jul 12, 2023
1 parent ec0e440 commit ed7a2a5
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 54 deletions.
22 changes: 22 additions & 0 deletions PowerType.Tests/CacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,26 @@ public void ShouldUpdate(TimeSpan? byTime, bool byCurrentWorkingDirectory, IEnum
result.Should().Be(shouldUpdate);
}
}

[InlineData("install", true)]
[InlineData("Install", true)]
[InlineData("Install package", true)]
[InlineData("reinstall", false)]
[InlineData("uninstall", true)]
[Theory]
public void ShouldUpdateByCommand(string command, bool expected)
{
var cache = new Cache
{
ByCommand = new string[]
{
"install",
"uninstall"
}
};
var systemTime = new SystemTimeMock(DateTime.Today);
cache.Initialize(systemTime);
cache.UpdateCache(new List<SourceItem> { }, string.Empty);
cache.ShouldUpdate(string.Empty, command).Should().Be(expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
using Xunit;

namespace PowerType.Tests;
public class DictionarySuggestorTests
public class DictionarySuggesterTests
{
private readonly DictionarySuggestor dictionarySuggestor;
public DictionarySuggestorTests()
private readonly DictionarySuggester dictionarySuggester;
public DictionarySuggesterTests()
{
var allRepositories = new DynamicSource
{
Expand Down Expand Up @@ -135,7 +135,7 @@ public DictionarySuggestorTests()
{
SourceItem.FromName("First"), SourceItem.FromName("Second"), SourceItem.FromName("With space"), SourceItem.FromName("release/v1.16.26")
}, "");
dictionarySuggestor = new DictionarySuggestor(dictionary);
dictionarySuggester = new DictionarySuggester(dictionary);
}

[Theory]
Expand Down Expand Up @@ -178,7 +178,7 @@ public void TestParsing(string[] input, string[] expectedOutput)
{
var context = new DictionaryParsingContext("", input.Select(x => PowerShellString.FromEscapedSmart(x)));
context.Command = new Command("git", null!);
var result = dictionarySuggestor.GetPredictions(context).Select(x => x.SuggestionText);
var result = dictionarySuggester.GetPredictions(context).Select(x => x.SuggestionText);
result.Should().BeEquivalentTo(expectedOutput);
}
}
Expand Down
6 changes: 3 additions & 3 deletions PowerType.Tests/ExecutionEngineThreadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void CacheDictionaryDynamicSources()
using var executionEngineThread = new ExecutionEngineThread(queue);
SendAndWaitForCommand(new InitializeDictionaryCommand(Path.Combine(Environment.CurrentDirectory, "Dictionaries", "test.ps1")), queue, executionEngineThread);
var dictionary = executionEngineThread.GetDictionaries().Single();
SendAndWaitForCommand(new CacheDictionaryDynamicSources(dictionary, Environment.CurrentDirectory), queue, executionEngineThread);
SendAndWaitForCommand(new CacheDictionaryDynamicSourcesCommand(dictionary, Environment.CurrentDirectory), queue, executionEngineThread);
if (!executionEngineThread.IsHealthy(out var exception))
{
throw new Exception("execution engine thread was not healthy", exception);
Expand All @@ -51,7 +51,7 @@ public void GetSuggestors()
var queue = new ThreadQueue<Command>();
using var executionEngineThread = new ExecutionEngineThread(queue);
SendAndWaitForCommand(new InitializeDictionaryCommand(Path.Combine(Environment.CurrentDirectory, "Dictionaries", "test.ps1")), queue, executionEngineThread);
executionEngineThread.GetSuggestors().Count.Should().Be(1);
executionEngineThread.GetSuggesters().Count.Should().Be(1);
}

[Fact]
Expand All @@ -62,7 +62,7 @@ public void DontCrashOnUnkownDrive()
SendAndWaitForCommand(new InitializeDictionaryCommand(Path.Combine(Environment.CurrentDirectory, "Dictionaries", "test.ps1")), queue, executionEngineThread);

var dictionary = executionEngineThread.GetDictionaries().Single();
SendAndWaitForCommand(new CacheDictionaryDynamicSources(dictionary, @"X:\DoesNotExist"), queue, executionEngineThread);
SendAndWaitForCommand(new CacheDictionaryDynamicSourcesCommand(dictionary, @"X:\DoesNotExist"), queue, executionEngineThread);
if (!executionEngineThread.IsHealthy(out var exception))
{
throw new Exception("execution engine thread was not healthy", exception);
Expand Down
4 changes: 2 additions & 2 deletions PowerType/BackgroundProcessing/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ internal abstract record Command
public volatile bool IsDone;
}
internal record InitializeDictionaryCommand(string File) : Command;
internal record CacheDictionaryDynamicSources(PowerTypeDictionary Dictionary, string CurrentWorkingDirectory) : Command;

internal record CacheDictionaryDynamicSourcesCommand(PowerTypeDictionary Dictionary, string CurrentWorkingDirectory) : Command;
internal record CommandExecutedCommand(PowerTypeDictionary Dictionary, string CurrentWorkingDirectory, string Command) : Command;
7 changes: 5 additions & 2 deletions PowerType/BackgroundProcessing/ExecutionEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal partial class ExecutionEngine
private ExecutionEngineThread? executionEngineThread;
private readonly ThreadQueue<Command> threadQueue = new();

public List<DictionarySuggestor> GetSuggestors() => executionEngineThread?.GetSuggestors() ?? new List<DictionarySuggestor>();
public List<DictionarySuggester> GetSuggesters() => executionEngineThread?.GetSuggesters() ?? new List<DictionarySuggester>();

public bool IsHealthy(out Exception? exception)
{
Expand Down Expand Up @@ -43,5 +43,8 @@ public void InitialDictionary(string dictionaryPath) =>
threadQueue.Enqueue(new InitializeDictionaryCommand(dictionaryPath));

public void Cache(PowerTypeDictionary dictionary, string currentWorkingDirectory) =>
threadQueue.Enqueue(new CacheDictionaryDynamicSources(dictionary, currentWorkingDirectory));
threadQueue.Enqueue(new CacheDictionaryDynamicSourcesCommand(dictionary, currentWorkingDirectory));

public void CommandExecuted(PowerTypeDictionary dictionary, string currentWorkingDirectory, string command) =>
threadQueue.Enqueue(new CommandExecutedCommand(dictionary, currentWorkingDirectory, command));
}
57 changes: 43 additions & 14 deletions PowerType/BackgroundProcessing/ExecutionEngineThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace PowerType.BackgroundProcessing;
internal class ExecutionEngineThread : IDisposable
{
private bool disposed;
record DictionaryInformation(string File, DictionarySuggestor Suggestor);
record DictionaryInformation(string File, DictionarySuggester Suggester);
private readonly object dictionariesLocker = new();
private readonly List<DictionaryInformation> dictionaries = new();
private readonly ThreadQueue<Command> queue;
Expand Down Expand Up @@ -39,11 +39,11 @@ public bool IsHealthy(out Exception? exception)
}

/// <summary>This method is thread safe</summary>
public List<DictionarySuggestor> GetSuggestors()
public List<DictionarySuggester> GetSuggesters()
{
lock (dictionariesLocker)
{
return dictionaries.ConvertAll(x => x.Suggestor);
return dictionaries.ConvertAll(x => x.Suggester);
}
}

Expand All @@ -52,7 +52,7 @@ public List<PowerTypeDictionary> GetDictionaries()
{
lock (dictionariesLocker)
{
return dictionaries.ConvertAll(x => x.Suggestor.Dictionary);
return dictionaries.ConvertAll(x => x.Suggester.Dictionary);
}
}

Expand All @@ -67,10 +67,14 @@ private void InnerLoop()
{
Handle(initializeCommand);
}
else if (command is CacheDictionaryDynamicSources cacheDictionaryCommand)
else if (command is CacheDictionaryDynamicSourcesCommand cacheDictionaryCommand)
{
Handle(cacheDictionaryCommand);
}
else if (command is CommandExecutedCommand commandExecutedCommand)
{
Handle(commandExecutedCommand);
}
else
{
throw new InvalidOperationException($"Don't know how to handle command {command.GetType()}!");
Expand Down Expand Up @@ -125,7 +129,7 @@ private void Handle(InitializeDictionaryCommand command)
throw new Exception("Failed to initialize dictionary, errors: " + errors);
}

DictionarySuggestor suggestor;
DictionarySuggester suggester;
var resultObject = result.Single().BaseObject;
if (resultObject is PowerTypeDictionary dictionary)
{
Expand All @@ -136,27 +140,27 @@ private void Handle(InitializeDictionaryCommand command)
}
dictionary.Initialize(SystemTime.Instance);
dictionary.Validate();
suggestor = new DictionarySuggestor(dictionary);
suggester = new DictionarySuggester(dictionary);
}
else if (resultObject is DictionarySuggestor suggestorTmp)
else if (resultObject is DictionarySuggester suggesterTemp)
{
suggestor = suggestorTmp;
suggester = suggesterTemp;
}
else
{
throw new InvalidOperationException("Didn't receive a PowerTypeDictionary or ISuggestor");
throw new InvalidOperationException("Didn't receive a PowerTypeDictionary or ISuggester");
}
lock (dictionariesLocker)
{
dictionaries.Add(new DictionaryInformation(command.File, suggestor));
dictionaries.Add(new DictionaryInformation(command.File, suggester));
}
}

private void Handle(CacheDictionaryDynamicSources command)
private void Handle(CacheDictionaryDynamicSourcesCommand command)
{
var dictionaryInformation = GetDictionaryInformation(command.Dictionary);

foreach (var dynamicSource in dictionaryInformation.Suggestor.GetDynamicSources())
foreach (var dynamicSource in dictionaryInformation.Suggester.GetDynamicSources())
{
if (dynamicSource.Cache.ShouldUpdate(command.CurrentWorkingDirectory))
{
Expand All @@ -177,11 +181,36 @@ private void Handle(CacheDictionaryDynamicSources command)
}
}

private void Handle(CommandExecutedCommand command)
{
var dictionaryInformation = GetDictionaryInformation(command.Dictionary);

foreach (var dynamicSource in dictionaryInformation.Suggester.GetDynamicSources())
{
if (dynamicSource.Cache.ShouldUpdate(command.CurrentWorkingDirectory, command.Command))
{
try
{
runspace.SessionStateProxy.Path.SetLocation(command.CurrentWorkingDirectory);
var result = dynamicSource.CommandExpression.Invoke();
var items = result.Select(x => x.BaseObject is string value ?
new SourceItem { Name = value } :
(SourceItem)x.BaseObject).ToList();
dynamicSource.Cache.UpdateCache(items, command.CurrentWorkingDirectory);
}
catch (System.Management.Automation.DriveNotFoundException)
{
//todo: log exception
}
}
}
}

private DictionaryInformation GetDictionaryInformation(PowerTypeDictionary dictionary)
{
lock (dictionariesLocker)
{
return this.dictionaries.Single(x => x.Suggestor is DictionarySuggestor suggestor && suggestor.Dictionary == dictionary);
return this.dictionaries.Single(x => x.Suggester is DictionarySuggester suggestor && suggestor.Dictionary == dictionary);
}
}

Expand Down
4 changes: 3 additions & 1 deletion PowerType/CurrentWorkingDirectoryProvider.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.Management.Automation;
using PowerType.BackgroundProcessing;

namespace PowerType;

public class CurrentWorkingDirectoryProvider : ICurrentWorkingDirectoryProvider
internal class CurrentWorkingDirectoryProvider : ICurrentWorkingDirectoryProvider
{
private bool disposed;
private volatile string currentWorkingDirectory;
Expand All @@ -13,6 +14,7 @@ public CurrentWorkingDirectoryProvider(SessionState sessionState)
this.sessionState = sessionState;
currentWorkingDirectory = sessionState.Path.CurrentFileSystemLocation.ProviderPath;
sessionState.InvokeCommand.LocationChangedAction += LocationChanged;

}

private void LocationChanged(object? sender, LocationChangedEventArgs e)
Expand Down
12 changes: 8 additions & 4 deletions PowerType/Dictionaries/git.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ $remotes = [DynamicSource]@{
};
Cache = [Cache]@{
ByCurrentWorkingDirectory = $true;
ByTime = New-TimeSpan -Seconds 30
ByTime = New-TimeSpan -Seconds 30;
ByCommand = @("remote")
}
}

Expand All @@ -85,7 +86,8 @@ $allBranches = [DynamicSource]@{
};
Cache = [Cache]@{
ByCurrentWorkingDirectory = $true;
ByTime = New-TimeSpan -Seconds 10
ByTime = New-TimeSpan -Seconds 10;
ByCommand = @("checkout", "fetch", "pull", "switch")
}
}

Expand All @@ -97,7 +99,8 @@ $localBranches = [DynamicSource]@{
};
Cache = [Cache]@{
ByCurrentWorkingDirectory = $true;
ByTime = New-TimeSpan -Seconds 10
ByTime = New-TimeSpan -Seconds 10;
ByCommand = @("checkout", "fetch", "pull", "switch")
}
}

Expand All @@ -109,7 +112,8 @@ $stashes = [DynamicSource]@{
};
Cache = [Cache]@{
ByCurrentWorkingDirectory = $true;
ByTime = New-TimeSpan -Seconds 10
ByTime = New-TimeSpan -Seconds 10;
ByCommand = @("stash")
}
}

Expand Down
3 changes: 2 additions & 1 deletion PowerType/Dictionaries/nvm4win.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
$hardcoded + $list
};
Cache = [Cache]@{
ByTime = New-TimeSpan -Seconds 10
ByTime = New-TimeSpan -Seconds 10;
ByCommand = @("install", "uninstall")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace PowerType;

internal class DictionarySuggestor
internal class DictionarySuggester
{
private readonly List<DynamicSource> dynamicSources;
public PowerTypeDictionary Dictionary { get; }
Expand All @@ -18,7 +18,7 @@ internal class DictionarySuggestor

public bool HasKey(string key) => Keys.Contains(key, StringComparer.OrdinalIgnoreCase);

public DictionarySuggestor(PowerTypeDictionary dictionary)
public DictionarySuggester(PowerTypeDictionary dictionary)
{
this.Dictionary = dictionary;
dynamicSources = dictionary.Parameters.SelectMany(p => RecursiveGet(p)).ToList();
Expand Down
10 changes: 7 additions & 3 deletions PowerType/Model/Cache.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace PowerType.Model;
using PowerType.BackgroundProcessing;

namespace PowerType.Model;

public interface ISystemTime
{
Expand All @@ -9,6 +11,7 @@ public class Cache
{
private ISystemTime systemTime = default!;
public TimeSpan? ByTime { get; set; }
public string[] ByCommand { get; set; } = new string[0];
public bool ByCurrentWorkingDirectory { get; set; }

internal void Initialize(ISystemTime systemTime)
Expand All @@ -33,13 +36,14 @@ internal void Validate()
private DateTime lastUpdate = DateTime.MinValue;
private string lastWokringDirectory = string.Empty;

internal bool ShouldUpdate(string currentWorkingDirectory)
internal bool ShouldUpdate(string currentWorkingDirectory, string? commandExecuted = null)
{
lock (locker)
{
var diff = systemTime.UtcNow.Subtract(lastUpdate);
return (ByTime.HasValue && diff > ByTime.Value) ||
(ByCurrentWorkingDirectory && lastWokringDirectory != currentWorkingDirectory);
(ByCurrentWorkingDirectory && lastWokringDirectory != currentWorkingDirectory) ||
(commandExecuted != null && ByCommand.Any(x => commandExecuted.StartsWith(x, StringComparison.OrdinalIgnoreCase)));
}
}

Expand Down
Loading

0 comments on commit ed7a2a5

Please sign in to comment.