mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 11:16:51 +02:00
Rename the [Ee]xts dir to ext (#38852)
**WARNING:** This PR will probably blow up all in-flight PRs at some point in the early days of CmdPal, two of us created seperate `Exts` and `exts` dirs. Depending on what the casing was on the branch that you checked one of those out from, it'd get stuck like that on your PC forever. Windows didn't care, so we never noticed. But GitHub does care, and now browsing the source on GitHub is basically impossible. Closes #38081
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
public static class EventHandler
|
||||
{
|
||||
// To obtain the path of the app when multiple events are added to the Concurrent queue across multiple threads.
|
||||
// On the first occurrence of a different file path, the existing app path is to be returned without removing any more elements from the queue.
|
||||
public static async Task<string> GetAppPathFromQueueAsync(ConcurrentQueue<string> eventHandlingQueue, int dequeueDelay)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(eventHandlingQueue);
|
||||
|
||||
var previousAppPath = string.Empty;
|
||||
|
||||
// To obtain the last event associated with a particular app.
|
||||
while (eventHandlingQueue.TryPeek(out var currentAppPath))
|
||||
{
|
||||
// Using OrdinalIgnoreCase since this is used internally with paths
|
||||
if (string.IsNullOrEmpty(previousAppPath) || previousAppPath.Equals(currentAppPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// To dequeue a path only if it is the first one in the queue or if the path was the same as the previous one (to avoid trying to create apps on duplicate events)
|
||||
previousAppPath = currentAppPath;
|
||||
eventHandlingQueue.TryDequeue(out _);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// This delay has been added to account for the delay in events being triggered during app installation.
|
||||
await Task.Delay(dequeueDelay).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return previousAppPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
// File System Watcher Wrapper class which implements the IFileSystemWatcherWrapper interface
|
||||
public sealed class FileSystemWatcherWrapper : FileSystemWatcher, IFileSystemWatcherWrapper
|
||||
{
|
||||
public FileSystemWatcherWrapper()
|
||||
{
|
||||
}
|
||||
|
||||
Collection<string> IFileSystemWatcherWrapper.Filters
|
||||
{
|
||||
get => this.Filters;
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
foreach (var filter in value)
|
||||
{
|
||||
this.Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
public interface IFileSystemWatcherWrapper
|
||||
{
|
||||
// Events to watch out for
|
||||
event FileSystemEventHandler Created;
|
||||
|
||||
event FileSystemEventHandler Deleted;
|
||||
|
||||
event FileSystemEventHandler Changed;
|
||||
|
||||
event RenamedEventHandler Renamed;
|
||||
|
||||
// Properties of File System watcher
|
||||
Collection<string> Filters { get; set; }
|
||||
|
||||
bool EnableRaisingEvents { get; set; }
|
||||
|
||||
NotifyFilters NotifyFilter { get; set; }
|
||||
|
||||
string Path { get; set; }
|
||||
|
||||
bool IncludeSubdirectories { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
internal interface IProgramRepository
|
||||
{
|
||||
void IndexPrograms();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
public interface IRepository<T>
|
||||
{
|
||||
void Add(T insertedItem);
|
||||
|
||||
void Remove(T removedItem);
|
||||
|
||||
bool Contains(T item);
|
||||
|
||||
void SetList(IList<T> list);
|
||||
|
||||
bool Any();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">typeof</typeparam>
|
||||
public class ListRepository<T> : IRepository<T>, IEnumerable<T>
|
||||
{
|
||||
public IList<T> Items
|
||||
{
|
||||
get { return _items.Values.ToList(); }
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<int, T> _items = new ConcurrentDictionary<int, T>();
|
||||
|
||||
public ListRepository()
|
||||
{
|
||||
}
|
||||
|
||||
public void SetList(IList<T> list)
|
||||
{
|
||||
// enforce that internal representation
|
||||
try
|
||||
{
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
_items = new ConcurrentDictionary<int, T>(list.ToDictionary(i => i.GetHashCode()));
|
||||
#pragma warning restore CS8602 // Dereference of a possibly null reference.
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public bool Any()
|
||||
{
|
||||
return !_items.IsEmpty;
|
||||
}
|
||||
|
||||
public void Add(T insertedItem)
|
||||
{
|
||||
if (insertedItem is not null)
|
||||
{
|
||||
if (!_items.TryAdd(insertedItem.GetHashCode(), insertedItem))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(T removedItem)
|
||||
{
|
||||
if (removedItem is not null)
|
||||
{
|
||||
if (!_items.TryRemove(removedItem.GetHashCode(), out _))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ParallelQuery<T> AsParallel()
|
||||
{
|
||||
return _items.Values.AsParallel();
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
if (item is not null)
|
||||
{
|
||||
return _items.ContainsKey(item.GetHashCode());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _items.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _items.GetEnumerator();
|
||||
}
|
||||
|
||||
public int Count()
|
||||
{
|
||||
return _items.Count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps.
|
||||
/// This repository will also monitor for changes to the PackageCatalog and update the repository accordingly
|
||||
/// </summary>
|
||||
internal sealed class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
|
||||
{
|
||||
private readonly IPackageCatalog _packageCatalog;
|
||||
|
||||
private bool _isDirty;
|
||||
|
||||
public bool ShouldReload()
|
||||
{
|
||||
return _isDirty;
|
||||
}
|
||||
|
||||
public void ResetReloadFlag()
|
||||
{
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
// private readonly PluginInitContext _context;
|
||||
public PackageRepository(IPackageCatalog packageCatalog)
|
||||
{
|
||||
_packageCatalog = packageCatalog ?? throw new ArgumentNullException(nameof(packageCatalog), "PackageRepository expects an interface to be able to subscribe to package events");
|
||||
|
||||
_packageCatalog.PackageInstalling += OnPackageInstalling;
|
||||
_packageCatalog.PackageUninstalling += OnPackageUninstalling;
|
||||
_packageCatalog.PackageUpdating += OnPackageUpdating;
|
||||
}
|
||||
|
||||
public void OnPackageInstalling(PackageCatalog p, PackageInstallingEventArgs args)
|
||||
{
|
||||
if (args.IsComplete)
|
||||
{
|
||||
AddPackage(args.Package);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPackageUninstalling(PackageCatalog p, PackageUninstallingEventArgs args)
|
||||
{
|
||||
if (args.Progress == 0)
|
||||
{
|
||||
RemovePackage(args.Package);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPackageUpdating(PackageCatalog p, PackageUpdatingEventArgs args)
|
||||
{
|
||||
if (args.Progress == 0)
|
||||
{
|
||||
RemovePackage(args.SourcePackage);
|
||||
}
|
||||
|
||||
if (args.IsComplete)
|
||||
{
|
||||
AddPackage(args.TargetPackage);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPackage(Package package)
|
||||
{
|
||||
var packageWrapper = PackageWrapper.GetWrapperFromPackage(package);
|
||||
if (string.IsNullOrEmpty(packageWrapper.InstalledLocation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var uwp = new UWP(packageWrapper);
|
||||
uwp.InitializeAppInfo(packageWrapper.InstalledLocation);
|
||||
foreach (var app in uwp.Apps)
|
||||
{
|
||||
app.UpdateLogoPath(ThemeHelper.GetCurrentTheme());
|
||||
Add(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePackage(Package package)
|
||||
{
|
||||
// find apps associated with this package.
|
||||
var packageWrapper = PackageWrapper.GetWrapperFromPackage(package);
|
||||
var uwp = new UWP(packageWrapper);
|
||||
var apps = Items.Where(a => a.Package.Equals(uwp)).ToArray();
|
||||
|
||||
foreach (var app in apps)
|
||||
{
|
||||
Remove(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void IndexPrograms()
|
||||
{
|
||||
var windows10 = new Version(10, 0);
|
||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
||||
|
||||
var applications = support ? Programs.UWP.All() : Array.Empty<UWPApplication>();
|
||||
|
||||
SetList(applications);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
internal sealed class Win32ProgramFileSystemWatchers : IDisposable
|
||||
{
|
||||
public string[] PathsToWatch { get; set; }
|
||||
|
||||
public List<FileSystemWatcherWrapper> FileSystemWatchers { get; set; }
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
// This class contains the list of directories to watch and initializes the File System Watchers
|
||||
public Win32ProgramFileSystemWatchers()
|
||||
{
|
||||
PathsToWatch = GetPathsToWatch();
|
||||
|
||||
FileSystemWatchers = new List<FileSystemWatcherWrapper>();
|
||||
for (var index = 0; index < PathsToWatch.Length; index++)
|
||||
{
|
||||
FileSystemWatchers.Add(new FileSystemWatcherWrapper());
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an array of paths to be watched
|
||||
private static string[] GetPathsToWatch()
|
||||
{
|
||||
var paths = new string[]
|
||||
{
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory),
|
||||
};
|
||||
|
||||
var invalidPaths = new List<string>();
|
||||
foreach (var path in paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.GetFiles(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
invalidPaths.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return paths.Except(invalidPaths).ToArray();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
for (var index = 0; index < PathsToWatch.Length; index++)
|
||||
{
|
||||
FileSystemWatchers[index].Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Win32Program = Microsoft.CmdPal.Ext.Apps.Programs.Win32Program;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
internal sealed class Win32ProgramRepository : ListRepository<Programs.Win32Program>, IProgramRepository
|
||||
{
|
||||
private static readonly IFileSystem FileSystem = new FileSystem();
|
||||
private static readonly IPath Path = FileSystem.Path;
|
||||
|
||||
private const string LnkExtension = ".lnk";
|
||||
private const string UrlExtension = ".url";
|
||||
|
||||
private AllAppsSettings _settings;
|
||||
private IList<IFileSystemWatcherWrapper> _fileSystemWatcherHelpers;
|
||||
private string[] _pathsToWatch;
|
||||
private int _numberOfPathsToWatch;
|
||||
private Collection<string> extensionsToWatch = new Collection<string> { "*.exe", $"*{LnkExtension}", "*.appref-ms", $"*{UrlExtension}" };
|
||||
|
||||
private bool _isDirty;
|
||||
|
||||
private static ConcurrentQueue<string> commonEventHandlingQueue = new ConcurrentQueue<string>();
|
||||
|
||||
public Win32ProgramRepository(IList<IFileSystemWatcherWrapper> fileSystemWatcherHelpers, AllAppsSettings settings, string[] pathsToWatch)
|
||||
{
|
||||
_fileSystemWatcherHelpers = fileSystemWatcherHelpers;
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings), "Win32ProgramRepository requires an initialized settings object");
|
||||
_pathsToWatch = pathsToWatch;
|
||||
_numberOfPathsToWatch = pathsToWatch.Length;
|
||||
InitializeFileSystemWatchers();
|
||||
|
||||
// This task would always run in the background trying to dequeue file paths from the queue at regular intervals.
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var dequeueDelay = 500;
|
||||
var appPath = await EventHandler.GetAppPathFromQueueAsync(commonEventHandlingQueue, dequeueDelay).ConfigureAwait(false);
|
||||
|
||||
// To allow for the installation process to finish.
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(appPath))
|
||||
{
|
||||
Win32Program? app = Win32Program.GetAppFromPath(appPath);
|
||||
if (app != null)
|
||||
{
|
||||
Add(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public bool ShouldReload()
|
||||
{
|
||||
return _isDirty;
|
||||
}
|
||||
|
||||
public void ResetReloadFlag()
|
||||
{
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
private void InitializeFileSystemWatchers()
|
||||
{
|
||||
for (var index = 0; index < _numberOfPathsToWatch; index++)
|
||||
{
|
||||
// To set the paths to monitor
|
||||
_fileSystemWatcherHelpers[index].Path = _pathsToWatch[index];
|
||||
|
||||
// to be notified when there is a change to a file
|
||||
_fileSystemWatcherHelpers[index].NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
|
||||
|
||||
// filtering the app types that we want to monitor
|
||||
_fileSystemWatcherHelpers[index].Filters = extensionsToWatch;
|
||||
|
||||
// Registering the event handlers
|
||||
_fileSystemWatcherHelpers[index].Created += OnAppCreated;
|
||||
_fileSystemWatcherHelpers[index].Deleted += OnAppDeleted;
|
||||
_fileSystemWatcherHelpers[index].Renamed += OnAppRenamed;
|
||||
_fileSystemWatcherHelpers[index].Changed += OnAppChanged;
|
||||
|
||||
// Enable the file system watcher
|
||||
_fileSystemWatcherHelpers[index].EnableRaisingEvents = true;
|
||||
|
||||
// Enable it to search in sub folders as well
|
||||
_fileSystemWatcherHelpers[index].IncludeSubdirectories = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnAppRenamedAsync(object sender, RenamedEventArgs e)
|
||||
{
|
||||
var oldPath = e.OldFullPath;
|
||||
var newPath = e.FullPath;
|
||||
|
||||
// fix for https://github.com/microsoft/PowerToys/issues/34391
|
||||
// the msi installer creates a shortcut, which is detected by the PT Run and ends up in calling this OnAppRenamed method
|
||||
// the thread needs to be halted for a short time to avoid locking the new shortcut file as we read it, otherwise the lock causes
|
||||
// in the issue scenario that a warning is popping up during the msi install process.
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
var extension = Path.GetExtension(newPath);
|
||||
Win32Program.ApplicationType oldAppType = Win32Program.GetAppTypeFromPath(oldPath);
|
||||
Programs.Win32Program? newApp = Win32Program.GetAppFromPath(newPath);
|
||||
Programs.Win32Program? oldApp = null;
|
||||
|
||||
// Once the shortcut application is renamed, the old app does not exist and therefore when we try to get the FullPath we get the lnk path instead of the exe path
|
||||
// This changes the hashCode() of the old application.
|
||||
// Therefore, instead of retrieving the old app using the GetAppFromPath(), we construct the application ourself
|
||||
// This situation is not encountered for other application types because the fullPath is the path itself, instead of being computed by using the path to the app.
|
||||
try
|
||||
{
|
||||
if (oldAppType == Win32Program.ApplicationType.ShortcutApplication || oldAppType == Win32Program.ApplicationType.InternetShortcutApplication)
|
||||
{
|
||||
oldApp = new Win32Program() { Name = Path.GetFileNameWithoutExtension(e.OldName) ?? string.Empty, ExecutableName = Path.GetFileName(e.OldName) ?? string.Empty, FullPath = newApp?.FullPath ?? oldPath };
|
||||
}
|
||||
else
|
||||
{
|
||||
oldApp = Win32Program.GetAppFromPath(oldPath);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
// To remove the old app which has been renamed and to add the new application.
|
||||
if (oldApp != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(oldApp.Name) || string.IsNullOrWhiteSpace(oldApp.ExecutableName) || string.IsNullOrWhiteSpace(oldApp.FullPath))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(oldApp);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (newApp != null)
|
||||
{
|
||||
Add(newApp);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAppRenamed(object sender, RenamedEventArgs e)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await OnAppRenamedAsync(sender, e).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnAppDeleted(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
var path = e.FullPath;
|
||||
var extension = Path.GetExtension(path);
|
||||
Win32Program? app = null;
|
||||
|
||||
try
|
||||
{
|
||||
// To mitigate the issue of not having a FullPath for a shortcut app, we iterate through the items and find the app with the same hashcode.
|
||||
// Using OrdinalIgnoreCase since this is used internally
|
||||
if (extension.Equals(LnkExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
app = GetAppWithSameLnkFilePath(path);
|
||||
if (app == null)
|
||||
{
|
||||
// Cancelled links won't have a resolved path.
|
||||
app = GetAppWithSameNameAndExecutable(Path.GetFileNameWithoutExtension(path), Path.GetFileName(path));
|
||||
}
|
||||
}
|
||||
else if (extension.Equals(UrlExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
app = GetAppWithSameNameAndExecutable(Path.GetFileNameWithoutExtension(path), Path.GetFileName(path));
|
||||
}
|
||||
else
|
||||
{
|
||||
app = Programs.Win32Program.GetAppFromPath(path);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
if (app != null)
|
||||
{
|
||||
Remove(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// When a URL application is deleted, we can no longer get the HashCode directly from the path because the FullPath a Url app is the URL obtained from reading the file
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1309:Use ordinal string comparison", Justification = "Using CurrentCultureIgnoreCase since application names could be dependent on currentculture See: https://github.com/microsoft/PowerToys/pull/5847/files#r468245190")]
|
||||
private Win32Program? GetAppWithSameNameAndExecutable(string name, string executableName)
|
||||
{
|
||||
foreach (Win32Program app in Items)
|
||||
{
|
||||
// Using CurrentCultureIgnoreCase since application names could be dependent on currentculture See: https://github.com/microsoft/PowerToys/pull/5847/files#r468245190
|
||||
if (name.Equals(app.Name, StringComparison.CurrentCultureIgnoreCase) && executableName.Equals(app.ExecutableName, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// To mitigate the issue faced (as stated above) when a shortcut application is renamed, the Exe FullPath and executable name must be obtained.
|
||||
// Unlike the rename event args, since we do not have a newPath, we iterate through all the programs and find the one with the same LnkResolved path.
|
||||
private Programs.Win32Program? GetAppWithSameLnkFilePath(string lnkFilePath)
|
||||
{
|
||||
foreach (Programs.Win32Program app in Items)
|
||||
{
|
||||
// Using Invariant / OrdinalIgnoreCase since we're comparing paths
|
||||
if (lnkFilePath.ToUpperInvariant().Equals(app.LnkFilePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnAppCreated(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
var path = e.FullPath;
|
||||
|
||||
// Using OrdinalIgnoreCase since we're comparing extensions
|
||||
if (!Path.GetExtension(path).Equals(UrlExtension, StringComparison.OrdinalIgnoreCase) && !Path.GetExtension(path).Equals(LnkExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Programs.Win32Program? app = Programs.Win32Program.GetAppFromPath(path);
|
||||
if (app != null)
|
||||
{
|
||||
Add(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAppChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
var path = e.FullPath;
|
||||
|
||||
// Using OrdinalIgnoreCase since we're comparing extensions
|
||||
if (Path.GetExtension(path).Equals(UrlExtension, StringComparison.OrdinalIgnoreCase) || Path.GetExtension(path).Equals(LnkExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// When a url or lnk app is installed, multiple created and changed events are triggered.
|
||||
// To prevent the code from acting on the first such event (which may still be during app installation), the events are added a common queue and dequeued by a background task at regular intervals - https://github.com/microsoft/PowerToys/issues/6429.
|
||||
commonEventHandlingQueue.Enqueue(path);
|
||||
}
|
||||
}
|
||||
|
||||
public void IndexPrograms()
|
||||
{
|
||||
var applications = Programs.Win32Program.All(_settings);
|
||||
|
||||
SetList(applications);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user