Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
691cb405fd |
@@ -69,6 +69,8 @@
|
||||
<MenuItem Header="Reset to Defaults" Click="ResetConfig_Click"/>
|
||||
<MenuItem Header="Reload Config" Click="ReloadConfig_Click"/>
|
||||
<MenuItem Header="Open Config Folder" Click="OpenConfigFolder_Click"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="Validate Targets" Click="ValidateTargets_Click"/>
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
</Button>
|
||||
|
||||
@@ -22,6 +22,7 @@ public partial class MainWindow : Window
|
||||
private List<int>? selectedPath;
|
||||
private string? loadedAutoIconPath;
|
||||
private System.Windows.Point dragStartPoint;
|
||||
private sealed record TargetValidationIssue(string ItemPath, string Problem);
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
@@ -1343,6 +1344,159 @@ public partial class MainWindow : Window
|
||||
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)
|
||||
{
|
||||
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 or Settings items to temporarily disable them
|
||||
- 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 items in Settings to pin them to the top of their menu
|
||||
- Per-item icon and display mode
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<Authors>Ray Berezowski</Authors>
|
||||
<ApplicationIcon>Assets\AppIcon.ico</ApplicationIcon>
|
||||
<Version>1.0.1</Version>
|
||||
<AssemblyVersion>1.0.1.9</AssemblyVersion>
|
||||
<FileVersion>1.0.1.9</FileVersion>
|
||||
<AssemblyVersion>1.0.1.10</AssemblyVersion>
|
||||
<FileVersion>1.0.1.10</FileVersion>
|
||||
<InformationalVersion>1.0.1</InformationalVersion>
|
||||
</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 **Disable Item** or **Enable Item** to hide or restore it
|
||||
- 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
|
||||
- Back up and restore `config.json`
|
||||
- Enable Start with Windows
|
||||
|
||||
Reference in New Issue
Block a user