Add target validation action
This commit is contained in:
@@ -69,6 +69,8 @@
|
|||||||
<MenuItem Header="Reset to Defaults" Click="ResetConfig_Click"/>
|
<MenuItem Header="Reset to Defaults" Click="ResetConfig_Click"/>
|
||||||
<MenuItem Header="Reload Config" Click="ReloadConfig_Click"/>
|
<MenuItem Header="Reload Config" Click="ReloadConfig_Click"/>
|
||||||
<MenuItem Header="Open Config Folder" Click="OpenConfigFolder_Click"/>
|
<MenuItem Header="Open Config Folder" Click="OpenConfigFolder_Click"/>
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="Validate Targets" Click="ValidateTargets_Click"/>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</Button.ContextMenu>
|
</Button.ContextMenu>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public partial class MainWindow : Window
|
|||||||
private List<int>? selectedPath;
|
private List<int>? selectedPath;
|
||||||
private string? loadedAutoIconPath;
|
private string? loadedAutoIconPath;
|
||||||
private System.Windows.Point dragStartPoint;
|
private System.Windows.Point dragStartPoint;
|
||||||
|
private sealed record TargetValidationIssue(string ItemPath, string Problem);
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
@@ -1343,6 +1344,159 @@ public partial class MainWindow : Window
|
|||||||
ConfigService.OpenConfigFolder();
|
ConfigService.OpenConfigFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ValidateTargets_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (controller is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyConfigFields();
|
||||||
|
List<TargetValidationIssue> issues = [];
|
||||||
|
ValidateTargets(controller.Config.Items, [], issues);
|
||||||
|
|
||||||
|
if (issues.Count == 0)
|
||||||
|
{
|
||||||
|
StatusText.Text = "Validation complete. No broken targets found.";
|
||||||
|
MessageBox.Show("No broken targets found.", "Taskbar Launcher");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string message = $"Found {issues.Count} possible broken target(s):\n\n" +
|
||||||
|
string.Join("\n", issues.Take(20).Select(issue => $"- {issue.ItemPath}: {issue.Problem}"));
|
||||||
|
if (issues.Count > 20)
|
||||||
|
{
|
||||||
|
message += $"\n\nShowing first 20 of {issues.Count}.";
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusText.Text = $"Validation found {issues.Count} possible broken target(s).";
|
||||||
|
MessageBox.Show(message, "Taskbar Launcher");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateTargets(IEnumerable<LauncherItem> items, List<string> parentNames, List<TargetValidationIssue> issues)
|
||||||
|
{
|
||||||
|
foreach (LauncherItem item in items)
|
||||||
|
{
|
||||||
|
List<string> itemPath = [.. parentNames, string.IsNullOrWhiteSpace(item.Title) ? "Untitled" : item.Title];
|
||||||
|
if (!item.IsDisabled && TryGetTargetIssue(item, out string? problem) && problem is not null)
|
||||||
|
{
|
||||||
|
issues.Add(new TargetValidationIssue(string.Join(" > ", itemPath), problem));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Children.Count > 0)
|
||||||
|
{
|
||||||
|
ValidateTargets(item.Children, itemPath, issues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetTargetIssue(LauncherItem item, out string? problem)
|
||||||
|
{
|
||||||
|
problem = null;
|
||||||
|
|
||||||
|
if (item.Type == LauncherItemType.Menu || item.IsDisabled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(item.Target))
|
||||||
|
{
|
||||||
|
problem = "Target is blank.";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string target = Environment.ExpandEnvironmentVariables(item.Target.Trim());
|
||||||
|
switch (item.Type)
|
||||||
|
{
|
||||||
|
case LauncherItemType.Folder:
|
||||||
|
if (!Directory.Exists(target))
|
||||||
|
{
|
||||||
|
problem = "Folder does not exist.";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case LauncherItemType.Website:
|
||||||
|
if (!Uri.TryCreate(target, UriKind.Absolute, out Uri? uri) ||
|
||||||
|
(uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
|
||||||
|
{
|
||||||
|
problem = "Website target is not a valid http or https URL.";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case LauncherItemType.App:
|
||||||
|
if (!File.Exists(target) && !CommandExistsOnPath(target))
|
||||||
|
{
|
||||||
|
problem = "Application or shortcut was not found.";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case LauncherItemType.File:
|
||||||
|
if (!File.Exists(target))
|
||||||
|
{
|
||||||
|
problem = "File does not exist.";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
problem = "Item type is not recognized.";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CommandExistsOnPath(string target)
|
||||||
|
{
|
||||||
|
if (target.IndexOfAny([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]) >= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? pathValue = Environment.GetEnvironmentVariable("PATH");
|
||||||
|
if (string.IsNullOrWhiteSpace(pathValue))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string extension = Path.GetExtension(target);
|
||||||
|
IEnumerable<string> candidateNames = string.IsNullOrWhiteSpace(extension)
|
||||||
|
? GetExecutableCandidateNames(target)
|
||||||
|
: [target];
|
||||||
|
|
||||||
|
foreach (string directory in pathValue.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||||
|
{
|
||||||
|
foreach (string candidateName in candidateNames)
|
||||||
|
{
|
||||||
|
string candidatePath = Path.Combine(directory, candidateName);
|
||||||
|
if (File.Exists(candidatePath))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetExecutableCandidateNames(string target)
|
||||||
|
{
|
||||||
|
string? pathExtValue = Environment.GetEnvironmentVariable("PATHEXT");
|
||||||
|
string[] extensions = string.IsNullOrWhiteSpace(pathExtValue)
|
||||||
|
? [".EXE", ".CMD", ".BAT", ".COM"]
|
||||||
|
: pathExtValue.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
|
||||||
|
foreach (string extension in extensions)
|
||||||
|
{
|
||||||
|
yield return target + extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void About_Click(object sender, RoutedEventArgs e)
|
private void About_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
AboutWindow aboutWindow = new()
|
AboutWindow aboutWindow = new()
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Download the compiled Windows release from:
|
|||||||
- Right-click launcher entries to pin or unpin them from the top
|
- Right-click launcher entries to pin or unpin them from the top
|
||||||
- Right-click launcher entries or Settings items to temporarily disable them
|
- Right-click launcher entries or Settings items to temporarily disable them
|
||||||
- Right-click app/file entries to open their containing folder
|
- Right-click app/file entries to open their containing folder
|
||||||
|
- Validate missing app, file, and folder targets from Settings
|
||||||
- Right-click item duplication in Settings
|
- Right-click item duplication in Settings
|
||||||
- Right-click items in Settings to pin them to the top of their menu
|
- Right-click items in Settings to pin them to the top of their menu
|
||||||
- Per-item icon and display mode
|
- Per-item icon and display mode
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
<Authors>Ray Berezowski</Authors>
|
<Authors>Ray Berezowski</Authors>
|
||||||
<ApplicationIcon>Assets\AppIcon.ico</ApplicationIcon>
|
<ApplicationIcon>Assets\AppIcon.ico</ApplicationIcon>
|
||||||
<Version>1.0.1</Version>
|
<Version>1.0.1</Version>
|
||||||
<AssemblyVersion>1.0.1.9</AssemblyVersion>
|
<AssemblyVersion>1.0.1.10</AssemblyVersion>
|
||||||
<FileVersion>1.0.1.9</FileVersion>
|
<FileVersion>1.0.1.10</FileVersion>
|
||||||
<InformationalVersion>1.0.1</InformationalVersion>
|
<InformationalVersion>1.0.1</InformationalVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ From Settings you can:
|
|||||||
- Right-click an item and choose **Pin to Top** to show it first in its menu
|
- Right-click an item and choose **Pin to Top** to show it first in its menu
|
||||||
- Right-click an item and choose **Disable Item** or **Enable Item** to hide or restore it
|
- Right-click an item and choose **Disable Item** or **Enable Item** to hide or restore it
|
||||||
- Right-click an app or file item and choose **Open Containing Folder**
|
- Right-click an app or file item and choose **Open Containing Folder**
|
||||||
|
- Use **Config... > Validate Targets** to check for missing app, file, and folder targets
|
||||||
- Set per-item icon sizes
|
- Set per-item icon sizes
|
||||||
- Back up and restore `config.json`
|
- Back up and restore `config.json`
|
||||||
- Enable Start with Windows
|
- Enable Start with Windows
|
||||||
|
|||||||
Reference in New Issue
Block a user