diff --git a/PowerToys.sln b/PowerToys.sln index 52175309c5..d71848ac4f 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -255,8 +255,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerTest", "src\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedCommon", "src\common\ManagedCommon\ManagedCommon.csproj", "{4AED67B6-55FD-486F-B917-E543DEE2CB3C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Plugin.Program.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.Program.UnitTests\Microsoft.Plugin.Program.UnitTests.csproj", "{42851751-CBC8-45A6-97F5-7A0753F7B4D1}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -503,10 +501,6 @@ Global {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Debug|x64.Build.0 = Debug|x64 {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|x64.ActiveCfg = Release|x64 {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|x64.Build.0 = Release|x64 - {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|x64.ActiveCfg = Debug|x64 - {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|x64.Build.0 = Debug|x64 - {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|x64.ActiveCfg = Release|x64 - {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -577,7 +571,6 @@ Global {E6410BFC-B341-498C-8C67-312C20CDD8D5} = {1AFB6476-670D-4E80-A464-657E01DFF482} {62173D9A-6724-4C00-A1C8-FB646480A9EC} = {38BDB927-829B-4C65-9CD9-93FB05D66D65} {4AED67B6-55FD-486F-B917-E543DEE2CB3C} = {1AFB6476-670D-4E80-A464-657E01DFF482} - {42851751-CBC8-45A6-97F5-7A0753F7B4D1} = {4AFC9975-2456-4C70-94A4-84073C1CED93} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Microsoft.Plugin.Program.UnitTests.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Microsoft.Plugin.Program.UnitTests.csproj deleted file mode 100644 index 57e8108df5..0000000000 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Microsoft.Plugin.Program.UnitTests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - netcoreapp3.1 - - false - - x64 - - - - x64 - - - - - - - - - - - - - - diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Storage/ListRepositoryTests.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Storage/ListRepositoryTests.cs deleted file mode 100644 index fd1953419e..0000000000 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Storage/ListRepositoryTests.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Microsoft.Plugin.Program.Storage; -using Moq; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.Media.Capture; -using Wox.Infrastructure.Storage; - -namespace Microsoft.Plugin.Program.UnitTests.Storage -{ - [TestFixture] - class ListRepositoryTests - { - - [Test] - public void Contains_ShouldReturnTrue_WhenListIsInitializedWithItem() - { - //Arrange - var itemName = "originalItem1"; - IRepository repository = new ListRepository() { itemName }; - - //Act - var result = repository.Contains(itemName); - - //Assert - Assert.IsTrue(result); - } - - [Test] - public void Contains_ShouldReturnTrue_WhenListIsUpdatedWithAdd() - { - //Arrange - IRepository repository = new ListRepository(); - - //Act - var itemName = "newItem"; - repository.Add(itemName); - var result = repository.Contains(itemName); - - //Assert - Assert.IsTrue(result); - } - - [Test] - public void Contains_ShouldReturnFalse_WhenListIsUpdatedWithRemove() - { - //Arrange - var itemName = "originalItem1"; - IRepository repository = new ListRepository() { itemName }; - - //Act - repository.Remove(itemName); - var result = repository.Contains(itemName); - - //Assert - Assert.IsFalse(result); - } - - [Test] - public async Task Add_ShouldNotThrow_WhenBeingIterated() - { - //Arrange - ListRepository repository = new ListRepository(); - var numItems = 1000; - for(var i=0; i - { - var remainingIterations = 10000; - while (remainingIterations > 0) - { - foreach (var item in repository) - { - //keep iterating - - } - --remainingIterations; - } - - }); - - //Act - Insert on another thread - var addTask = Task.Run(() => - { - for (var i = 0; i < numItems; ++i) - { - repository.Add($"NewItem_{i}"); - } - }); - - //Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating - Assert.DoesNotThrowAsync(async () => - { - await Task.WhenAll(new Task[] { iterationTask, addTask }); - }); - } - - [Test] - public async Task Remove_ShouldNotThrow_WhenBeingIterated() - { - //Arrange - ListRepository repository = new ListRepository(); - var numItems = 1000; - for (var i = 0; i < numItems; ++i) - { - repository.Add($"OriginalItem_{i}"); - } - - //Act - Begin iterating on one thread - var iterationTask = Task.Run(() => - { - var remainingIterations = 10000; - while (remainingIterations > 0) - { - foreach (var item in repository) - { - //keep iterating - - } - --remainingIterations; - } - - }); - - //Act - Remove on another thread - var addTask = Task.Run(() => - { - for (var i = 0; i < numItems; ++i) - { - repository.Remove($"OriginalItem_{i}"); - } - }); - - //Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating - Assert.DoesNotThrowAsync(async () => - { - await Task.WhenAll(new Task[] { iterationTask, addTask }); - }); - } - } -} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Storage/PackageRepositoryTest.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Storage/PackageRepositoryTest.cs deleted file mode 100644 index 23b30552af..0000000000 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Storage/PackageRepositoryTest.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Plugin.Program.UnitTests.Storage -{ - class PackageRepositoryTest - { - } -} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs index ddd45bc1ab..ab4d90f477 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs @@ -1,185 +1,207 @@ -using Microsoft.PowerToys.Settings.UI.Lib; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Timers; -using System.Windows.Controls; -using Wox.Infrastructure.Logger; -using Wox.Infrastructure.Storage; -using Wox.Plugin; -using Microsoft.Plugin.Program.Views; - -using Stopwatch = Wox.Infrastructure.Stopwatch; -using Windows.ApplicationModel; -using Microsoft.Plugin.Program.Storage; -using Microsoft.Plugin.Program.Programs; - -namespace Microsoft.Plugin.Program -{ - public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable, IDisposable - { - private static readonly object IndexLock = new object(); - internal static Programs.Win32[] _win32s { get; set; } - internal static Settings _settings { get; set; } - - private static bool IsStartupIndexProgramsRequired => _settings.LastIndexTime.AddDays(3) < DateTime.Today; - - private static PluginInitContext _context; - - private static BinaryStorage _win32Storage; - private readonly PluginJsonStorage _settingsStorage; +using Microsoft.PowerToys.Settings.UI.Lib; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Timers; +using System.Windows.Controls; +using Wox.Infrastructure.Logger; +using Wox.Infrastructure.Storage; +using Wox.Plugin; +using Microsoft.Plugin.Program.Views; + +using Stopwatch = Wox.Infrastructure.Stopwatch; +using Microsoft.Plugin.Program.Programs; + +namespace Microsoft.Plugin.Program +{ + public class Main : ISettingProvider, IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable, IDisposable + { + private static readonly object IndexLock = new object(); + internal static Programs.Win32[] _win32s { get; set; } + internal static Programs.UWP.Application[] _uwps { get; set; } + internal static Settings _settings { get; set; } + + FileSystemWatcher _watcher = null; + System.Timers.Timer _timer = null; + + private static bool IsStartupIndexProgramsRequired => _settings.LastIndexTime.AddDays(3) < DateTime.Today; + + private static PluginInitContext _context; + + private static BinaryStorage _win32Storage; + private static BinaryStorage _uwpStorage; + private readonly PluginJsonStorage _settingsStorage; private bool _disposed = false; - private PackageRepository _packageRepository = new PackageRepository(new PackageCatalogWrapper(), new BinaryStorage>("UWP")); - - public Main() - { - _settingsStorage = new PluginJsonStorage(); - _settings = _settingsStorage.Load(); - - Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Preload programs cost", () => - { - _win32Storage = new BinaryStorage("Win32"); - _win32s = _win32Storage.TryLoad(new Programs.Win32[] { }); - - _packageRepository.Load(); - }); - Log.Info($"|Microsoft.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); - - var a = Task.Run(() => - { - if (IsStartupIndexProgramsRequired || !_win32s.Any()) - Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs); - }); - - var b = Task.Run(() => - { - if (IsStartupIndexProgramsRequired || !_packageRepository.Any()) - Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", _packageRepository.IndexPrograms); - }); - - - Task.WaitAll(a, b); - - _settings.LastIndexTime = DateTime.Today; - } - - public void Save() - { - _settingsStorage.Save(); - _win32Storage.Save(_win32s); - _packageRepository.Save(); - } - - public List Query(Query query) - { - Programs.Win32[] win32; - - lock (IndexLock) - { - // just take the reference inside the lock to eliminate query time issues. - win32 = _win32s; - } - - var results1 = win32.AsParallel() - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)); - - var results2 = _packageRepository.AsParallel() - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)); - - var result = results1.Concat(results2).Where(r => r != null && r.Score > 0).ToList(); - return result; - } - - public void Init(PluginInitContext context) - { - _context = context; - _context.API.ThemeChanged += OnThemeChanged; - UpdateUWPIconPath(_context.API.GetCurrentTheme()); - } - - public void OnThemeChanged(Theme _, Theme currentTheme) - { - UpdateUWPIconPath(currentTheme); - } - - public void UpdateUWPIconPath(Theme theme) - { - foreach (UWP.Application app in _packageRepository) - { - app.UpdatePath(theme); - } - } - - public static void IndexWin32Programs() - { - var win32S = Programs.Win32.All(_settings); - lock (IndexLock) - { - _win32s = win32S; - } - } - - - - public void IndexPrograms() - { - var t1 = Task.Run(() => IndexWin32Programs()); - var t2 = Task.Run(() => _packageRepository.IndexPrograms()); - - Task.WaitAll(t1, t2); - - _settings.LastIndexTime = DateTime.Today; - } - - public string GetTranslatedPluginTitle() - { - return _context.API.GetTranslation("wox_plugin_program_plugin_name"); - } - - public string GetTranslatedPluginDescription() - { - return _context.API.GetTranslation("wox_plugin_program_plugin_description"); - } - - public List LoadContextMenus(Result selectedResult) - { - var menuOptions = new List(); - var program = selectedResult.ContextData as Programs.IProgram; - if (program != null) - { - menuOptions = program.ContextMenus(_context.API); - } - - return menuOptions; - } - - public static void StartProcess(Func runProcess, ProcessStartInfo info) - { - try - { - runProcess(info); - } - catch (Exception) - { - var name = "Plugin: Program"; - var message = $"Unable to start: {info.FileName}"; - _context.API.ShowMsg(name, message, string.Empty); - } - } - - public void ReloadData() - { - IndexPrograms(); - } - - public void UpdateSettings(PowerLauncherSettings settings) - { - } + + public Main() + { + _settingsStorage = new PluginJsonStorage(); + _settings = _settingsStorage.Load(); + + Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Preload programs cost", () => + { + _win32Storage = new BinaryStorage("Win32"); + _win32s = _win32Storage.TryLoad(new Programs.Win32[] { }); + _uwpStorage = new BinaryStorage("UWP"); + _uwps = _uwpStorage.TryLoad(new Programs.UWP.Application[] { }); + }); + Log.Info($"|Microsoft.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); + Log.Info($"|Microsoft.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>"); + + var a = Task.Run(() => + { + if (IsStartupIndexProgramsRequired || !_win32s.Any()) + Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs); + }); + + var b = Task.Run(() => + { + if (IsStartupIndexProgramsRequired || !_uwps.Any()) + Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", IndexUWPPrograms); + }); + + Task.WaitAll(a, b); + + _settings.LastIndexTime = DateTime.Today; + + InitializeFileWatchers(); + InitializeTimer(); + } + + public void Save() + { + _settingsStorage.Save(); + _win32Storage.Save(_win32s); + _uwpStorage.Save(_uwps); + } + + public List Query(Query query) + { + Programs.Win32[] win32; + Programs.UWP.Application[] uwps; + + lock (IndexLock) + { + // just take the reference inside the lock to eliminate query time issues. + win32 = _win32s; + uwps = _uwps; + } + + var results1 = win32.AsParallel() + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, _context.API)); + + var results2 = uwps.AsParallel() + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, _context.API)); + + var result = results1.Concat(results2).Where(r => r != null && r.Score > 0).ToList(); + return result; + } + + public void Init(PluginInitContext context) + { + _context = context; + _context.API.ThemeChanged += OnThemeChanged; + UpdateUWPIconPath(_context.API.GetCurrentTheme()); + } + + public void OnThemeChanged(Theme _, Theme currentTheme) + { + UpdateUWPIconPath(currentTheme); + } + + public void UpdateUWPIconPath(Theme theme) + { + foreach (UWP.Application app in _uwps) + { + app.UpdatePath(theme); + } + } + + public static void IndexWin32Programs() + { + var win32S = Programs.Win32.All(_settings); + lock (IndexLock) + { + _win32s = win32S; + } + } + + public static void IndexUWPPrograms() + { + var windows10 = new Version(10, 0); + var support = Environment.OSVersion.Version.Major >= windows10.Major; + + var applications = support ? Programs.UWP.All() : new Programs.UWP.Application[] { }; + lock (IndexLock) + { + _uwps = applications; + } + } + + public static void IndexPrograms() + { + var t1 = Task.Run(() => IndexWin32Programs()); + var t2 = Task.Run(() => IndexUWPPrograms()); + + Task.WaitAll(t1, t2); + + _settings.LastIndexTime = DateTime.Today; + } + + public Control CreateSettingPanel() + { + return new ProgramSetting(_context, _settings, _win32s, _uwps); + } + + public string GetTranslatedPluginTitle() + { + return _context.API.GetTranslation("wox_plugin_program_plugin_name"); + } + + public string GetTranslatedPluginDescription() + { + return _context.API.GetTranslation("wox_plugin_program_plugin_description"); + } + + public List LoadContextMenus(Result selectedResult) + { + var menuOptions = new List(); + var program = selectedResult.ContextData as Programs.IProgram; + if (program != null) + { + menuOptions = program.ContextMenus(_context.API); + } + + return menuOptions; + } + + public static void StartProcess(Func runProcess, ProcessStartInfo info) + { + try + { + runProcess(info); + } + catch (Exception) + { + var name = "Plugin: Program"; + var message = $"Unable to start: {info.FileName}"; + _context.API.ShowMsg(name, message, string.Empty); + } + } + + public void ReloadData() + { + IndexPrograms(); + } + + public void UpdateSettings(PowerLauncherSettings settings) + { + } public void Dispose() { @@ -199,5 +221,52 @@ namespace Microsoft.Plugin.Program } } - } + void InitializeFileWatchers() + { + // Create a new FileSystemWatcher and set its properties. + _watcher = new FileSystemWatcher(); + var resolvedPath = Environment.ExpandEnvironmentVariables("%ProgramFiles%"); + _watcher.Path = resolvedPath; + + //Filter to create and deletes of 'microsoft.system.package.metadata' directories. + _watcher.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.FileName; + _watcher.IncludeSubdirectories = true; + + // Add event handlers. + _watcher.Created += OnChanged; + _watcher.Deleted += OnChanged; + + // Begin watching. + _watcher.EnableRaisingEvents = true; + } + + void InitializeTimer() + { + //multiple file writes occur on install / uninstall. Adding a delay before actually indexing. + var delayInterval = 5000; + _timer = new System.Timers.Timer(delayInterval); + _timer.Enabled = true; + _timer.AutoReset = false; + _timer.Elapsed += FileWatchElapsedTimer; + _timer.Stop(); + } + + //When a watched directory changes then reset the timer. + private void OnChanged(object source, FileSystemEventArgs e) + { + Log.Debug($"|Microsoft.Plugin.Program.Main|Directory Changed: {e.FullPath} {e.ChangeType} - Resetting timer."); + _timer.Stop(); + _timer.Start(); + } + + private void FileWatchElapsedTimer(object sender, ElapsedEventArgs e) + { + Task.Run(() => + { + Log.Debug($"|Microsoft.Plugin.Program.Main| ReIndexing UWP Programs"); + IndexUWPPrograms(); + Log.Debug($"|Microsoft.Plugin.Program.Main| Done ReIndexing"); + }); + } + } } \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/IPackageCatalog.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/IPackageCatalog.cs deleted file mode 100644 index 14a80898dd..0000000000 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/IPackageCatalog.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Windows.ApplicationModel; -using Windows.Foundation; - -namespace Microsoft.Plugin.Program.Programs -{ - internal interface IPackageCatalog - { - event TypedEventHandler PackageInstalling; - event TypedEventHandler PackageUninstalling; - event TypedEventHandler PackageUpdating; - } -} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackageCatalogWrapper.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackageCatalogWrapper.cs deleted file mode 100644 index 228656f38d..0000000000 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackageCatalogWrapper.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Windows.ApplicationModel; -using Windows.Foundation; - -namespace Microsoft.Plugin.Program.Programs -{ - - /// - /// This is a simple wrapper class around the PackageCatalog to facilitate unit testing. - /// - internal class PackageCatalogWrapper : IPackageCatalog - { - PackageCatalog _packageCatalog; - - public PackageCatalogWrapper() - { - //Opens the catalog of packages that is available for the current user. - _packageCatalog = PackageCatalog.OpenForCurrentUser(); - } - - // - // Summary: - // Indicates that an app package is installing. - public event TypedEventHandler PackageInstalling - { - add - { - _packageCatalog.PackageInstalling += value; - } - - remove - { - _packageCatalog.PackageInstalling -= value; - } - } - - // - // Summary: - // Indicates that an app package is uninstalling. - public event TypedEventHandler PackageUninstalling - { - add - { - _packageCatalog.PackageUninstalling += value; - } - - remove - { - _packageCatalog.PackageUninstalling -= value; - } - } - - - // - // Summary: - // Indicates that an app package is updating. - public event TypedEventHandler PackageUpdating - { - add - { - _packageCatalog.PackageUpdating += value; - } - - remove - { - _packageCatalog.PackageUpdating -= value; - } - } - - } -} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs index d71b15f9ab..a266cf8d16 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs @@ -31,7 +31,7 @@ namespace Microsoft.Plugin.Program.Programs { public string Name { get; } public string FullName { get; } - public string FamilyName { get; } + public string FamilyName { get; } public string Location { get; set; } public Application[] Apps { get; set; } @@ -40,17 +40,24 @@ namespace Microsoft.Plugin.Program.Programs public UWP(Package package) { - + Location = package.InstalledLocation.Path; Name = package.Id.Name; FullName = package.Id.FullName; FamilyName = package.Id.FamilyName; + InitializeAppInfo(); + Apps = Apps.Where(a => + { + var valid = + !string.IsNullOrEmpty(a.UserModelId) && + !string.IsNullOrEmpty(a.DisplayName); + return valid; + }).ToArray(); } - public void InitializeAppInfo(string installedLocation) + private void InitializeAppInfo() { - Location = installedLocation; AppxPackageHelper _helper = new AppxPackageHelper(); - var path = Path.Combine(installedLocation, "AppxManifest.xml"); + var path = Path.Combine(Location, "AppxManifest.xml"); var namespaces = XmlNamespaces(path); InitPackageVersion(namespaces); @@ -69,17 +76,9 @@ namespace Microsoft.Plugin.Program.Programs { var app = new Application(_app, this); apps.Add(app); - } - - Apps = apps.Where(a => - { - var valid = - !string.IsNullOrEmpty(a.UserModelId) && - !string.IsNullOrEmpty(a.DisplayName) && - a.AppListEntry != "none"; - - return valid; - }).ToArray(); + } + + Apps = apps.Where(a => a.AppListEntry != "none").ToArray(); } else { @@ -155,14 +154,21 @@ namespace Microsoft.Plugin.Program.Programs try { u = new UWP(p); - u.InitializeAppInfo(p.InstalledLocation.Path); } +#if !DEBUG catch (Exception e) { ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and " + $"unable to convert Package to UWP for {p.Id.FullName}", e); return new Application[] { }; } +#endif +#if DEBUG //make developer aware and implement handling + catch + { + throw; + } +#endif return u.Apps; }).ToArray(); diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Storage/IProgramRepository.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Storage/IProgramRepository.cs deleted file mode 100644 index 7072cb6d71..0000000000 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Storage/IProgramRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Windows.ApplicationModel; - -namespace Microsoft.Plugin.Program.Storage -{ - internal interface IProgramRepository - { - void IndexPrograms(); - void Load(); - void Save(); - } -} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Storage/PackageRepository.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Storage/PackageRepository.cs deleted file mode 100644 index f62b0cb3a6..0000000000 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Storage/PackageRepository.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Microsoft.Plugin.Program.Logger; -using Microsoft.Plugin.Program.Programs; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.ApplicationModel; -using Wox.Infrastructure.Storage; - -namespace Microsoft.Plugin.Program.Storage -{ - /// - /// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps. - /// This repository will also monitor for changes to the PackageCatelog and update the repository accordingly - /// - internal class PackageRepository : ListRepository, IProgramRepository - { - private IStorage> _storage; - - private IPackageCatalog _packageCatalog; - public PackageRepository(IPackageCatalog packageCatalog, IStorage> storage) - { - _storage = storage ?? throw new ArgumentNullException("storage", "StorageRepository requires an initialized storage interface"); - _packageCatalog = packageCatalog ?? throw new ArgumentNullException("packageCatalog", "PackageRepository expects an interface to be able to subscribe to package events"); - _packageCatalog.PackageInstalling += OnPackageInstalling; - _packageCatalog.PackageUninstalling += OnPackageUninstalling; - } - - public void OnPackageInstalling(PackageCatalog p, PackageInstallingEventArgs args) - { - if (args.IsComplete) - { - - try - { - var uwp = new UWP(args.Package); - uwp.InitializeAppInfo(args.Package.InstalledLocation.Path); - foreach (var app in uwp.Apps) - { - Add(app); - } - } - //InitializeAppInfo will throw if there is no AppxManifest.xml for the package. - //Note there are sometimes multiple packages per product and this doesn't necessarily mean that we haven't found the app. - //eg. "Could not find file 'C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminalPreview_2020.616.45.0_neutral_~_8wekyb3d8bbwe\\AppxManifest.xml'." - - catch (System.IO.FileNotFoundException e) - { - ProgramLogger.LogException($"|UWP|OnPackageInstalling|{args.Package.InstalledLocation}|{e.Message}", e); - } - } - } - - public void OnPackageUninstalling(PackageCatalog p, PackageUninstallingEventArgs args) - { - if (args.Progress == 0) - { - //find apps associated with this package. - var uwp = new UWP(args.Package); - var apps = Items.Where(a => a.Package.Equals(uwp)).ToArray(); - foreach (var app in apps) - { - Remove(app); - } - } - } - - public void IndexPrograms() - { - var windows10 = new Version(10, 0); - var support = Environment.OSVersion.Version.Major >= windows10.Major; - - var applications = support ? Programs.UWP.All() : new Programs.UWP.Application[] { }; - Set(applications); - } - - public void Save() - { - _storage.Save(Items); - } - - public void Load() - { - var items = _storage.TryLoad(new Programs.UWP.Application[] { }); - Set(items); - } - } -} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs index 59105c0199..3285722da4 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs @@ -61,6 +61,20 @@ namespace Microsoft.Plugin.Program.Views.Commands Enabled = t1.Enabled } )); + + Main._uwps + .Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) + .ToList() + .ForEach(t1 => ProgramSetting.ProgramSettingDisplayList + .Add( + new ProgramSource + { + Name = t1.DisplayName, + Location = t1.Package.Location, + UniqueIdentifier = t1.UniqueIdentifier, + Enabled = t1.Enabled + } + )); } internal static void SetProgramSourcesStatus(this List list, List selectedProgramSourcesToDisable, bool status) @@ -74,6 +88,11 @@ namespace Microsoft.Plugin.Program.Views.Commands .Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status)) .ToList() .ForEach(t1 => t1.Enabled = status); + + Main._uwps + .Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status)) + .ToList() + .ForEach(t1 => t1.Enabled = status); } internal static void StoreDisabledInSettings(this List list) @@ -114,7 +133,7 @@ namespace Microsoft.Plugin.Program.Views.Commands internal static bool IsReindexRequired(this List selectedItems) { - if (selectedItems.Where(t1 => t1.Enabled).Count() > 0 + if (selectedItems.Where(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0 && selectedItems.Where(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0) return true; diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Views/ProgramSetting.xaml.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Views/ProgramSetting.xaml.cs index 7802eeb59b..79b5cbbc68 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Views/ProgramSetting.xaml.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Views/ProgramSetting.xaml.cs @@ -51,6 +51,7 @@ namespace Microsoft.Plugin.Program.Views Task.Run(() => { Dispatcher.Invoke(() => { indexingPanel.Visibility = Visibility.Visible; }); + Main.IndexPrograms(); Dispatcher.Invoke(() => { indexingPanel.Visibility = Visibility.Hidden; }); }); } diff --git a/src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage.cs b/src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage.cs index 26c62a3a0b..83a41cf040 100644 --- a/src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage.cs +++ b/src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage.cs @@ -12,7 +12,7 @@ namespace Wox.Infrastructure.Storage /// Storage object using binary data /// Normally, it has better performance, but not readable /// - public class BinaryStorage : IStorage + public class BinaryStorage { // This storage helper returns whether or not to delete the binary storage items private static readonly int BINARY_STORAGE = 0; diff --git a/src/modules/launcher/Wox.Infrastructure/Storage/IRepository.cs b/src/modules/launcher/Wox.Infrastructure/Storage/IRepository.cs deleted file mode 100644 index ca8b9f1d26..0000000000 --- a/src/modules/launcher/Wox.Infrastructure/Storage/IRepository.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace Wox.Infrastructure.Storage -{ - public interface IRepository - { - void Add(T insertedItem); - void Remove(T removedItem); - bool Contains(T item); - void Set(IList list); - bool Any(); - } -} diff --git a/src/modules/launcher/Wox.Infrastructure/Storage/IStorage.cs b/src/modules/launcher/Wox.Infrastructure/Storage/IStorage.cs deleted file mode 100644 index 252cc8a9e9..0000000000 --- a/src/modules/launcher/Wox.Infrastructure/Storage/IStorage.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace Wox.Infrastructure.Storage -{ - public interface IStorage - { - /// - /// Saves the data - /// - /// - void Save(T data); - - /// - /// Attempts to load data, otherwise it will return the default provided - /// - /// - /// The loaded data or default - T TryLoad(T defaultData); - } -} diff --git a/src/modules/launcher/Wox.Infrastructure/Storage/ListRepository.cs b/src/modules/launcher/Wox.Infrastructure/Storage/ListRepository.cs deleted file mode 100644 index bcd820b778..0000000000 --- a/src/modules/launcher/Wox.Infrastructure/Storage/ListRepository.cs +++ /dev/null @@ -1,80 +0,0 @@ -using NLog.Filters; -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Controls.Primitives; -using Wox.Infrastructure; -using Wox.Infrastructure.Logger; - -namespace Wox.Infrastructure.Storage -{ - /// - /// The intent of this class is to provide a basic subset of 'list' like operations, without exposing callers to the internal representation - /// of the data structure. Currently this is implemented as a list for it's simplicity. - /// - /// - public class ListRepository : IRepository, IEnumerable - { - public IList Items { get { return _items.Values.ToList(); } } - - private ConcurrentDictionary _items = new ConcurrentDictionary(); - - public ListRepository() - { - - } - - public void Set(IList items) - { - //enforce that internal representation - _items = new ConcurrentDictionary(items.ToDictionary( i => i.GetHashCode())); - } - - public bool Any() - { - return _items.Any(); - } - - public void Add(T insertedItem) - { - if (!_items.TryAdd(insertedItem.GetHashCode(), insertedItem)) - { - Log.Error($"|ListRepository.Add| Item Already Exists <{insertedItem}>"); - } - - } - - public void Remove(T removedItem) - { - - if (!_items.TryRemove(removedItem.GetHashCode(), out _)) - { - Log.Error($"|ListRepository.Remove| Item Not Found <{removedItem}>"); - } - } - - public ParallelQuery AsParallel() - { - return _items.Values.AsParallel(); - } - - public bool Contains(T item) - { - return _items.ContainsKey(item.GetHashCode()); - } - - public IEnumerator GetEnumerator() - { - return _items.Values.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _items.GetEnumerator(); - } - } -} diff --git a/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs b/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs index 43140ed944..6ba0fe006b 100644 --- a/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs +++ b/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs @@ -2,10 +2,8 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Runtime.CompilerServices; using static Wox.Infrastructure.StringMatcher; -[assembly: InternalsVisibleToAttribute("Microsoft.Plugin.Program.UnitTests")] namespace Wox.Infrastructure { public class StringMatcher diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Programs/Win32Tests.cs b/src/modules/launcher/Wox.Test/Plugins/Win32Tests.cs similarity index 95% rename from src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Programs/Win32Tests.cs rename to src/modules/launcher/Wox.Test/Plugins/Win32Tests.cs index b12db73ae1..f8054e7f68 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program.UnitTests/Programs/Win32Tests.cs +++ b/src/modules/launcher/Wox.Test/Plugins/Win32Tests.cs @@ -1,437 +1,439 @@ -using Moq; -using NUnit.Framework; -using System.Collections.Generic; -using System.Linq; -using Wox.Infrastructure; -using Wox.Plugin; - -using Microsoft.Plugin.Program; -using System.IO.Packaging; -using Windows.ApplicationModel; -namespace Microsoft.Plugin.Program.UnitTests.Programs -{ - using Win32 = Microsoft.Plugin.Program.Programs.Win32; - - [TestFixture] +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using NUnit.Framework; +using Wox.Infrastructure; +using Wox.Plugin; +using Microsoft.Plugin.Program.Programs; +using Moq; +using System.IO; +using Microsoft.Plugin.Program; +using System.IO.Packaging; +using Windows.ApplicationModel; + +namespace Wox.Test.Plugins +{ + [TestFixture] public class Win32Tests - { - static Win32 notepad_appdata = new Win32 - { - Name = "Notepad", - ExecutableName = "notepad.exe", - FullPath = "c:\\windows\\system32\\notepad.exe", - LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk", - AppType = 2 - }; - - static Win32 notepad_users = new Win32 - { - Name = "Notepad", - ExecutableName = "notepad.exe", - FullPath = "c:\\windows\\system32\\notepad.exe", - LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk", - AppType = 2 - }; - - static Win32 azure_command_prompt = new Win32 - { - Name = "Microsoft Azure Command Prompt - v2.9", - ExecutableName = "cmd.exe", - FullPath = "c:\\windows\\system32\\cmd.exe", - LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft azure\\microsoft azure sdk for .net\\v2.9\\microsoft azure command prompt - v2.9.lnk", - AppType = 2 - }; - - static Win32 visual_studio_command_prompt = new Win32 - { - Name = "x64 Native Tools Command Prompt for VS 2019", - ExecutableName = "cmd.exe", - FullPath = "c:\\windows\\system32\\cmd.exe", - LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\visual studio 2019\\visual studio tools\\vc\\x64 native tools command prompt for vs 2019.lnk", - AppType = 2 - }; - - static Win32 command_prompt = new Win32 - { - Name = "Command Prompt", - ExecutableName = "cmd.exe", - FullPath = "c:\\windows\\system32\\cmd.exe", - LnkResolvedPath ="c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\command prompt.lnk", - AppType = 2 - }; - - static Win32 file_explorer = new Win32 - { - Name = "File Explorer", - ExecutableName = "File Explorer.lnk", - FullPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\file explorer.lnk", - LnkResolvedPath = null, - AppType = 2 - }; - - static Win32 wordpad = new Win32 - { - Name = "Wordpad", - ExecutableName = "wordpad.exe", - FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe", - LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\wordpad.lnk", - AppType = 2 - }; - - static Win32 wordpad_duplicate = new Win32 - { - Name = "WORDPAD", - ExecutableName = "WORDPAD.EXE", - FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe", - LnkResolvedPath = null, - AppType = 2 - }; - - static Win32 twitter_pwa = new Win32 - { - Name = "Twitter", - FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome_proxy.exe", - LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\chrome apps\\twitter.lnk", - Arguments = " --profile-directory=Default --app-id=jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi", - AppType = 0 - }; - - static Win32 pinned_webpage = new Win32 - { - Name = "Web page", - FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe", - LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\web page.lnk", - Arguments = "--profile-directory=Default --app-id=homljgmgpmcbpjbnjpfijnhipfkiclkd", - AppType = 0 - }; - - static Win32 edge_named_pinned_webpage = new Win32 - { - Name = "edge - Bing", - FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe", - LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\edge - bing.lnk", - Arguments = " --profile-directory=Default --app-id=aocfnapldcnfbofgmbbllojgocaelgdd", - AppType = 0 - }; - - static Win32 msedge = new Win32 - { - Name = "Microsoft Edge", - ExecutableName = "msedge.exe", - FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge.exe", - LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft edge.lnk", - AppType = 2 - }; - - static Win32 chrome = new Win32 - { - Name = "Google Chrome", - ExecutableName = "chrome.exe", - FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome.exe", - LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\google chrome.lnk", - AppType = 2 - }; - - static Win32 dummy_proxy_app = new Win32 - { - Name = "Proxy App", - ExecutableName = "test_proxy.exe", - FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\test_proxy.exe", - LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\test proxy.lnk", - AppType = 2 - }; - - static Win32 cmd_run_command = new Win32 - { - Name = "cmd", - ExecutableName = "cmd.exe", - FullPath = "c:\\windows\\system32\\cmd.exe", - LnkResolvedPath = null, - AppType = 3 // Run command - }; - - static Win32 cmder_run_command = new Win32 - { - Name = "Cmder", - Description = "Cmder: Lovely Console Emulator", - ExecutableName = "Cmder.exe", - FullPath = "c:\\tools\\cmder\\cmder.exe", - LnkResolvedPath = null, - AppType = 3 // Run command - }; - - static Win32 dummy_internetShortcut_app = new Win32 - { - Name = "Shop Titans", - ExecutableName = "Shop Titans.url", - FullPath = "steam://rungameid/1258080", - ParentDirectory = "C:\\Users\\temp\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Steam", - LnkResolvedPath = null, - AppType = 1 - }; - - static Win32 dummy_internetShortcut_app_duplicate = new Win32 - { - Name = "Shop Titans", - ExecutableName = "Shop Titans.url", - FullPath = "steam://rungameid/1258080", - ParentDirectory = "C:\\Users\\temp\\Desktop", - LnkResolvedPath = null, - AppType = 1 - }; - - [Test] - public void DedupFunction_whenCalled_mustRemoveDuplicateNotepads() - { - // Arrange - List prgms = new List(); - prgms.Add(notepad_appdata); - prgms.Add(notepad_users); - - // Act - Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); - - // Assert - Assert.AreEqual(apps.Length, 1); - } - - [Test] - public void DedupFunction_whenCalled_MustRemoveInternetShortcuts() - { - // Arrange - List prgms = new List(); - prgms.Add(dummy_internetShortcut_app); - prgms.Add(dummy_internetShortcut_app_duplicate); - - // Act - Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); - - // Assert - Assert.AreEqual(apps.Length, 1); - } - - [Test] - public void DedupFunction_whenCalled_mustNotRemovelnkWhichdoesNotHaveExe() - { - // Arrange - List prgms = new List(); - prgms.Add(file_explorer); - - // Act - Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); - - // Assert - Assert.AreEqual(apps.Length, 1); - } - - [Test] - public void DedupFunction_mustRemoveDuplicates_forExeExtensionsWithoutLnkResolvedPath() - { - // Arrange - List prgms = new List(); - prgms.Add(wordpad); - prgms.Add(wordpad_duplicate); - - // Act - Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); - - // Assert - Assert.AreEqual(apps.Length, 1); - Assert.IsTrue(!string.IsNullOrEmpty(apps[0].LnkResolvedPath)); - } - - [Test] - public void DedupFunction_mustNotRemovePrograms_withSameExeNameAndFullPath() - { - // Arrange - List prgms = new List(); - prgms.Add(azure_command_prompt); - prgms.Add(visual_studio_command_prompt); - prgms.Add(command_prompt); - - // Act - Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); - - // Assert - Assert.AreEqual(apps.Length, 3); - } - - [Test] - public void FunctionIsWebApplication_ShouldReturnTrue_ForWebApplications() - { - // The IsWebApplication(() function must return true for all PWAs and pinned web pages - Assert.IsTrue(twitter_pwa.IsWebApplication()); - Assert.IsTrue(pinned_webpage.IsWebApplication()); - Assert.IsTrue(edge_named_pinned_webpage.IsWebApplication()); - - // Should not filter apps whose executable name ends with proxy.exe - Assert.IsFalse(dummy_proxy_app.IsWebApplication()); - } - - [TestCase("ignore")] - public void FunctionFilterWebApplication_ShouldReturnFalse_WhenSearchingForTheMainApp(string query) - { - // Irrespective of the query, the FilterWebApplication() Function must not filter main apps such as edge and chrome - Assert.IsFalse(msedge.FilterWebApplication(query)); - Assert.IsFalse(chrome.FilterWebApplication(query)); - } - - [TestCase("edge", ExpectedResult = true)] - [TestCase("EDGE", ExpectedResult = true)] - [TestCase("msedge", ExpectedResult = true)] - [TestCase("Microsoft", ExpectedResult = true)] - [TestCase("edg", ExpectedResult = true)] - [TestCase("Edge page", ExpectedResult = false)] - [TestCase("Edge Web page", ExpectedResult = false)] - public bool EdgeWebSites_ShouldBeFiltered_WhenSearchingForEdge(string query) - { - return pinned_webpage.FilterWebApplication(query); - } - - [TestCase("chrome", ExpectedResult = true)] - [TestCase("CHROME", ExpectedResult = true)] - [TestCase("Google", ExpectedResult = true)] - [TestCase("Google Chrome", ExpectedResult = true)] - [TestCase("Google Chrome twitter", ExpectedResult = false)] - public bool ChromeWebSites_ShouldBeFiltered_WhenSearchingForChrome(string query) - { - return twitter_pwa.FilterWebApplication(query); - } - - [TestCase("twitter", 0, ExpectedResult = false)] - [TestCase("Twit", 0, ExpectedResult = false)] - [TestCase("TWITTER", 0, ExpectedResult = false)] - [TestCase("web", 1, ExpectedResult = false)] - [TestCase("Page", 1, ExpectedResult = false)] - [TestCase("WEB PAGE", 1, ExpectedResult = false)] - [TestCase("edge", 2, ExpectedResult = false)] - [TestCase("EDGE", 2, ExpectedResult = false)] - public bool PinnedWebPages_ShouldNotBeFiltered_WhenSearchingForThem(string query, int Case) - { - const uint CASE_TWITTER = 0; - const uint CASE_WEB_PAGE = 1; - const uint CASE_EDGE_NAMED_WEBPAGE = 2; - - // If the query is a part of the name of the web application, it should not be filtered, - // even if the name is the same as that of the main application, eg: case 2 - edge - if (Case == CASE_TWITTER) - { - return twitter_pwa.FilterWebApplication(query); - } - else if (Case == CASE_WEB_PAGE) - { - return pinned_webpage.FilterWebApplication(query); - } - else if (Case == CASE_EDGE_NAMED_WEBPAGE) - { - return edge_named_pinned_webpage.FilterWebApplication(query); - } - // unreachable code - return true; - } - - [TestCase("Command Prompt")] - [TestCase("cmd")] - [TestCase("cmd.exe")] - [TestCase("ignoreQueryText")] - public void Win32Applications_ShouldNotBeFiltered_WhenFilteringRunCommands(string query) - { - // Even if there is an exact match in the name or exe name, win32 applications should never be filtered - Assert.IsTrue(command_prompt.QueryEqualsNameForRunCommands(query)); - } - - [TestCase("cmd")] - [TestCase("Cmd")] - [TestCase("CMD")] - public void RunCommands_ShouldNotBeFiltered_OnExactMatch(string query) - { - // Partial matches should be filtered as cmd is not equal to cmder - Assert.IsFalse(cmder_run_command.QueryEqualsNameForRunCommands(query)); - - // the query matches the name (cmd) and is therefore not filtered (case-insensitive) - Assert.IsTrue(cmd_run_command.QueryEqualsNameForRunCommands(query)); - } - - [Test] - public void WEB_APPLICATION_ReturnContextMenuWithOpenInConsole_WhenContextMenusIsCalled() - { - // Arrange - var mock = new Mock(); - mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); - - // Act - List contextMenuResults = pinned_webpage.ContextMenus(mock.Object); - - // Assert - Assert.AreEqual(contextMenuResults.Count, 3); - mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once()); - mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once()); - mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once()); - } - - [Test] - public void INTERNET_SHORTCUT_APPLICATION_ReturnContextMenuWithOpenInConsole_WhenContextMenusIsCalled() - { - // Arrange - var mock = new Mock(); - mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); - - // Act - List contextMenuResults = dummy_internetShortcut_app.ContextMenus(mock.Object); - - // Assert - Assert.AreEqual(contextMenuResults.Count, 2); - mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once()); - mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once()); - } - - [Test] - public void WIN32_APPLICATION_ReturnContextMenuWithOpenInConsole_WhenContextMenusIsCalled() - { - // Arrange - var mock = new Mock(); - mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); - - // Act - List contextMenuResults = chrome.ContextMenus(mock.Object); - - // Assert - Assert.AreEqual(contextMenuResults.Count, 3); - mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once()); - mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once()); - mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once()); - } - - [Test] - public void RUN_COMMAND_ReturnContextMenuWithOpenInConsole_WhenContextMenusIsCalled() - { - // Arrange - var mock = new Mock(); - mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); - - // Act - List contextMenuResults = cmd_run_command.ContextMenus(mock.Object); - - // Assert - Assert.AreEqual(contextMenuResults.Count, 3); - mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once()); - mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once()); - mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once()); - } - - [Test] - public void Win32Apps_ShouldSetNameAsTitle_WhileCreatingResult() - { - var mock = new Mock(); - mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); - StringMatcher.Instance = new StringMatcher(); - - // Act - var result = cmder_run_command.Result("cmder", mock.Object); - - // Assert - Assert.IsTrue(result.Title.Equals(cmder_run_command.Name)); - Assert.IsFalse(result.Title.Equals(cmder_run_command.Description)); - } - } -} + { + static Win32 notepad_appdata = new Win32 + { + Name = "Notepad", + ExecutableName = "notepad.exe", + FullPath = "c:\\windows\\system32\\notepad.exe", + LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk", + AppType = 2 + }; + + static Win32 notepad_users = new Win32 + { + Name = "Notepad", + ExecutableName = "notepad.exe", + FullPath = "c:\\windows\\system32\\notepad.exe", + LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk", + AppType = 2 + }; + + static Win32 azure_command_prompt = new Win32 + { + Name = "Microsoft Azure Command Prompt - v2.9", + ExecutableName = "cmd.exe", + FullPath = "c:\\windows\\system32\\cmd.exe", + LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft azure\\microsoft azure sdk for .net\\v2.9\\microsoft azure command prompt - v2.9.lnk", + AppType = 2 + }; + + static Win32 visual_studio_command_prompt = new Win32 + { + Name = "x64 Native Tools Command Prompt for VS 2019", + ExecutableName = "cmd.exe", + FullPath = "c:\\windows\\system32\\cmd.exe", + LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\visual studio 2019\\visual studio tools\\vc\\x64 native tools command prompt for vs 2019.lnk", + AppType = 2 + }; + + static Win32 command_prompt = new Win32 + { + Name = "Command Prompt", + ExecutableName = "cmd.exe", + FullPath = "c:\\windows\\system32\\cmd.exe", + LnkResolvedPath ="c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\command prompt.lnk", + AppType = 2 + }; + + static Win32 file_explorer = new Win32 + { + Name = "File Explorer", + ExecutableName = "File Explorer.lnk", + FullPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\file explorer.lnk", + LnkResolvedPath = null, + AppType = 2 + }; + + static Win32 wordpad = new Win32 + { + Name = "Wordpad", + ExecutableName = "wordpad.exe", + FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe", + LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\wordpad.lnk", + AppType = 2 + }; + + static Win32 wordpad_duplicate = new Win32 + { + Name = "WORDPAD", + ExecutableName = "WORDPAD.EXE", + FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe", + LnkResolvedPath = null, + AppType = 2 + }; + + static Win32 twitter_pwa = new Win32 + { + Name = "Twitter", + FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome_proxy.exe", + LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\chrome apps\\twitter.lnk", + Arguments = " --profile-directory=Default --app-id=jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi", + AppType = 0 + }; + + static Win32 pinned_webpage = new Win32 + { + Name = "Web page", + FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe", + LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\web page.lnk", + Arguments = "--profile-directory=Default --app-id=homljgmgpmcbpjbnjpfijnhipfkiclkd", + AppType = 0 + }; + + static Win32 edge_named_pinned_webpage = new Win32 + { + Name = "edge - Bing", + FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe", + LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\edge - bing.lnk", + Arguments = " --profile-directory=Default --app-id=aocfnapldcnfbofgmbbllojgocaelgdd", + AppType = 0 + }; + + static Win32 msedge = new Win32 + { + Name = "Microsoft Edge", + ExecutableName = "msedge.exe", + FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge.exe", + LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft edge.lnk", + AppType = 2 + }; + + static Win32 chrome = new Win32 + { + Name = "Google Chrome", + ExecutableName = "chrome.exe", + FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome.exe", + LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\google chrome.lnk", + AppType = 2 + }; + + static Win32 dummy_proxy_app = new Win32 + { + Name = "Proxy App", + ExecutableName = "test_proxy.exe", + FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\test_proxy.exe", + LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\test proxy.lnk", + AppType = 2 + }; + + static Win32 cmd_run_command = new Win32 + { + Name = "cmd", + ExecutableName = "cmd.exe", + FullPath = "c:\\windows\\system32\\cmd.exe", + LnkResolvedPath = null, + AppType = 3 // Run command + }; + + static Win32 cmder_run_command = new Win32 + { + Name = "Cmder", + Description = "Cmder: Lovely Console Emulator", + ExecutableName = "Cmder.exe", + FullPath = "c:\\tools\\cmder\\cmder.exe", + LnkResolvedPath = null, + AppType = 3 // Run command + }; + + static Win32 dummy_internetShortcut_app = new Win32 + { + Name = "Shop Titans", + ExecutableName = "Shop Titans.url", + FullPath = "steam://rungameid/1258080", + ParentDirectory = "C:\\Users\\temp\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Steam", + LnkResolvedPath = null, + AppType = 1 + }; + + static Win32 dummy_internetShortcut_app_duplicate = new Win32 + { + Name = "Shop Titans", + ExecutableName = "Shop Titans.url", + FullPath = "steam://rungameid/1258080", + ParentDirectory = "C:\\Users\\temp\\Desktop", + LnkResolvedPath = null, + AppType = 1 + }; + + [Test] + public void DedupFunction_whenCalled_mustRemoveDuplicateNotepads() + { + // Arrange + List prgms = new List(); + prgms.Add(notepad_appdata); + prgms.Add(notepad_users); + + // Act + Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); + + // Assert + Assert.AreEqual(apps.Length, 1); + } + + [Test] + public void DedupFunction_whenCalled_MustRemoveInternetShortcuts() + { + // Arrange + List prgms = new List(); + prgms.Add(dummy_internetShortcut_app); + prgms.Add(dummy_internetShortcut_app_duplicate); + + // Act + Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); + + // Assert + Assert.AreEqual(apps.Length, 1); + } + + [Test] + public void DedupFunction_whenCalled_mustNotRemovelnkWhichdoesNotHaveExe() + { + // Arrange + List prgms = new List(); + prgms.Add(file_explorer); + + // Act + Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); + + // Assert + Assert.AreEqual(apps.Length, 1); + } + + [Test] + public void DedupFunction_mustRemoveDuplicates_forExeExtensionsWithoutLnkResolvedPath() + { + // Arrange + List prgms = new List(); + prgms.Add(wordpad); + prgms.Add(wordpad_duplicate); + + // Act + Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); + + // Assert + Assert.AreEqual(apps.Length, 1); + Assert.IsTrue(!string.IsNullOrEmpty(apps[0].LnkResolvedPath)); + } + + [Test] + public void DedupFunction_mustNotRemovePrograms_withSameExeNameAndFullPath() + { + // Arrange + List prgms = new List(); + prgms.Add(azure_command_prompt); + prgms.Add(visual_studio_command_prompt); + prgms.Add(command_prompt); + + // Act + Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel()); + + // Assert + Assert.AreEqual(apps.Length, 3); + } + + [Test] + public void FunctionIsWebApplication_ShouldReturnTrue_ForWebApplications() + { + // The IsWebApplication(() function must return true for all PWAs and pinned web pages + Assert.IsTrue(twitter_pwa.IsWebApplication()); + Assert.IsTrue(pinned_webpage.IsWebApplication()); + Assert.IsTrue(edge_named_pinned_webpage.IsWebApplication()); + + // Should not filter apps whose executable name ends with proxy.exe + Assert.IsFalse(dummy_proxy_app.IsWebApplication()); + } + + [TestCase("ignore")] + public void FunctionFilterWebApplication_ShouldReturnFalse_WhenSearchingForTheMainApp(string query) + { + // Irrespective of the query, the FilterWebApplication() Function must not filter main apps such as edge and chrome + Assert.IsFalse(msedge.FilterWebApplication(query)); + Assert.IsFalse(chrome.FilterWebApplication(query)); + } + + [TestCase("edge", ExpectedResult = true)] + [TestCase("EDGE", ExpectedResult = true)] + [TestCase("msedge", ExpectedResult = true)] + [TestCase("Microsoft", ExpectedResult = true)] + [TestCase("edg", ExpectedResult = true)] + [TestCase("Edge page", ExpectedResult = false)] + [TestCase("Edge Web page", ExpectedResult = false)] + public bool EdgeWebSites_ShouldBeFiltered_WhenSearchingForEdge(string query) + { + return pinned_webpage.FilterWebApplication(query); + } + + [TestCase("chrome", ExpectedResult = true)] + [TestCase("CHROME", ExpectedResult = true)] + [TestCase("Google", ExpectedResult = true)] + [TestCase("Google Chrome", ExpectedResult = true)] + [TestCase("Google Chrome twitter", ExpectedResult = false)] + public bool ChromeWebSites_ShouldBeFiltered_WhenSearchingForChrome(string query) + { + return twitter_pwa.FilterWebApplication(query); + } + + [TestCase("twitter", 0, ExpectedResult = false)] + [TestCase("Twit", 0, ExpectedResult = false)] + [TestCase("TWITTER", 0, ExpectedResult = false)] + [TestCase("web", 1, ExpectedResult = false)] + [TestCase("Page", 1, ExpectedResult = false)] + [TestCase("WEB PAGE", 1, ExpectedResult = false)] + [TestCase("edge", 2, ExpectedResult = false)] + [TestCase("EDGE", 2, ExpectedResult = false)] + public bool PinnedWebPages_ShouldNotBeFiltered_WhenSearchingForThem(string query, int Case) + { + const uint CASE_TWITTER = 0; + const uint CASE_WEB_PAGE = 1; + const uint CASE_EDGE_NAMED_WEBPAGE = 2; + + // If the query is a part of the name of the web application, it should not be filtered, + // even if the name is the same as that of the main application, eg: case 2 - edge + if (Case == CASE_TWITTER) + { + return twitter_pwa.FilterWebApplication(query); + } + else if(Case == CASE_WEB_PAGE) + { + return pinned_webpage.FilterWebApplication(query); + } + else if(Case == CASE_EDGE_NAMED_WEBPAGE) + { + return edge_named_pinned_webpage.FilterWebApplication(query); + } + // unreachable code + return true; + } + + [TestCase("Command Prompt")] + [TestCase("cmd")] + [TestCase("cmd.exe")] + [TestCase("ignoreQueryText")] + public void Win32Applications_ShouldNotBeFiltered_WhenFilteringRunCommands(string query) + { + // Even if there is an exact match in the name or exe name, applications should never be filtered + Assert.IsTrue(command_prompt.QueryEqualsNameForRunCommands(query)); + } + + [TestCase("cmd")] + [TestCase("Cmd")] + [TestCase("CMD")] + public void RunCommands_ShouldNotBeFiltered_OnExactMatch(string query) + { + // Partial matches should be filtered as cmd is not equal to cmder + Assert.IsFalse(cmder_run_command.QueryEqualsNameForRunCommands(query)); + + // the query matches the name (cmd) and is therefore not filtered (case-insensitive) + Assert.IsTrue(cmd_run_command.QueryEqualsNameForRunCommands(query)); + } + + [Test] + public void WEB_APPLICATION_ReturnContextMenuWithOpenInConsole_WhenContextMenusIsCalled() + { + // Arrange + var mock = new Mock(); + mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); + + // Act + List contextMenuResults = pinned_webpage.ContextMenus(mock.Object); + + // Assert + Assert.AreEqual(contextMenuResults.Count, 3); + mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once()); + mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once()); + mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once()); + } + + [Test] + public void INTERNET_SHORTCUT_APPLICATION_ReturnContextMenuWithOpenInConsole_WhenContextMenusIsCalled() + { + // Arrange + var mock = new Mock(); + mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); + + // Act + List contextMenuResults = dummy_internetShortcut_app.ContextMenus(mock.Object); + + // Assert + Assert.AreEqual(contextMenuResults.Count, 2); + mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once()); + mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once()); + } + + [Test] + public void WIN32_APPLICATION_ReturnContextMenuWithOpenInConsole_WhenContextMenusIsCalled() + { + // Arrange + var mock = new Mock(); + mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); + + // Act + List contextMenuResults = chrome.ContextMenus(mock.Object); + + // Assert + Assert.AreEqual(contextMenuResults.Count, 3); + mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once()); + mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once()); + mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once()); + } + + [Test] + public void RUN_COMMAND_ReturnContextMenuWithOpenInConsole_WhenContextMenusIsCalled() + { + // Arrange + var mock = new Mock(); + mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); + + // Act + List contextMenuResults = cmd_run_command.ContextMenus(mock.Object); + + // Assert + Assert.AreEqual(contextMenuResults.Count, 3); + mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once()); + mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once()); + mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once()); + } + + [Test] + public void Win32Apps_ShouldSetNameAsTitle_WhileCreatingResult() + { + var mock = new Mock(); + mock.Setup(x => x.GetTranslation(It.IsAny())).Returns(It.IsAny()); + StringMatcher.Instance = new StringMatcher(); + + // Act + var result = cmder_run_command.Result("cmder", mock.Object); + + // Assert + Assert.IsTrue(result.Title.Equals(cmder_run_command.Name)); + Assert.IsFalse(result.Title.Equals(cmder_run_command.Description)); + } + } +}