115 lines
5.0 KiB
C#
115 lines
5.0 KiB
C#
using System.IO;
|
||
using System.Windows;
|
||
using System.Windows.Threading;
|
||
|
||
namespace DeskCore;
|
||
|
||
/// <summary>Verknüpfungen zu deinstallierten Programmen automatisch aus den Kategorien entfernen.</summary>
|
||
public partial class MainWindow
|
||
{
|
||
private DispatcherTimer? _deadScanTimer;
|
||
private DispatcherTimer? _deadScanPeriodic;
|
||
private bool _deadScanRunning;
|
||
private DispatcherTimer? _deadConfirmTimer;
|
||
private readonly Dictionary<string, DateTime> _deadSince = new(StringComparer.OrdinalIgnoreCase);
|
||
private static readonly TimeSpan ConfirmAfter = TimeSpan.FromSeconds(60);
|
||
|
||
/// <summary>Zweite Prüfung kurz nach Ablauf der Wartezeit.</summary>
|
||
private void ScheduleConfirmScan()
|
||
{
|
||
if (_deadConfirmTimer is null)
|
||
{
|
||
_deadConfirmTimer = new DispatcherTimer { Interval = ConfirmAfter + TimeSpan.FromSeconds(5) };
|
||
_deadConfirmTimer.Tick += (_, _) => { _deadConfirmTimer.Stop(); CleanupDeadShortcuts(); };
|
||
}
|
||
if (!_deadConfirmTimer.IsEnabled) _deadConfirmTimer.Start();
|
||
}
|
||
|
||
/// <summary>Einmal beim Start einrichten: Prüfung kurz nach dem Start und danach alle 10 Minuten.</summary>
|
||
public void StartDeadShortcutWatch()
|
||
{
|
||
_deadScanTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(4) };
|
||
_deadScanTimer.Tick += (_, _) => { _deadScanTimer.Stop(); CleanupDeadShortcuts(); };
|
||
// Deinstallationen über die Windows-Einstellungen ändern unsere Ordner nicht → zusätzlich regelmäßig prüfen
|
||
_deadScanPeriodic = new DispatcherTimer { Interval = TimeSpan.FromMinutes(10) };
|
||
_deadScanPeriodic.Tick += (_, _) => CleanupDeadShortcuts();
|
||
_deadScanPeriodic.Start();
|
||
ScheduleDeadShortcutScan();
|
||
}
|
||
|
||
/// <summary>Nach Änderungen (z. B. Deinstallation entfernt die Desktop-Verknüpfung) gebündelt prüfen.</summary>
|
||
public void ScheduleDeadShortcutScan()
|
||
{
|
||
if (_deadScanTimer is null) return;
|
||
_deadScanTimer.Stop();
|
||
_deadScanTimer.Start();
|
||
}
|
||
|
||
private async void CleanupDeadShortcuts()
|
||
{
|
||
if (!S.RemoveDeadShortcuts || _deadScanRunning || IsBusy) return;
|
||
_deadScanRunning = true;
|
||
try
|
||
{
|
||
var keep = S.KeptShortcuts.ToList();
|
||
var found = await Task.Run(() => Storage.FindDeadShortcuts(keep));
|
||
|
||
// Updates ersetzen ihre Programmdatei manchmal kurz → erst entfernen, wenn sie mindestens
|
||
// eine Minute lang fehlt (zweite Prüfung), sonst würde ein laufendes Update fälschlich aufgeräumt
|
||
var now = DateTime.UtcNow;
|
||
foreach (var p in _deadSince.Keys.Where(k => !found.Contains(k, StringComparer.OrdinalIgnoreCase)).ToList())
|
||
_deadSince.Remove(p);
|
||
foreach (var p in found) _deadSince.TryAdd(p, now);
|
||
var dead = found.Where(p => now - _deadSince[p] >= ConfirmAfter).ToList();
|
||
if (dead.Count < found.Count) ScheduleConfirmScan();
|
||
if (dead.Count == 0) return;
|
||
foreach (var p in dead) _deadSince.Remove(p);
|
||
|
||
var entry = new UndoEntry(dead.Count == 1
|
||
? $"„{Storage.DisplayName(dead[0])}“ entfernen (Programm deinstalliert)"
|
||
: $"{dead.Count} Verknüpfungen deinstallierter Programme entfernen");
|
||
var removed = new List<string>();
|
||
foreach (var p in dead)
|
||
{
|
||
try
|
||
{
|
||
// Reihenfolge beim Rückgängigmachen: erst zurückholen, dann merken, dass sie bleiben soll
|
||
var path = p;
|
||
entry.Add(() =>
|
||
{
|
||
if (!S.KeptShortcuts.Contains(path, StringComparer.OrdinalIgnoreCase)) S.KeptShortcuts.Add(path);
|
||
S.Save();
|
||
return null;
|
||
});
|
||
entry.AddDelete(p, Storage.Delete(p));
|
||
removed.Add(p);
|
||
}
|
||
catch { }
|
||
}
|
||
if (removed.Count == 0) return;
|
||
Undo.Push(entry);
|
||
Refresh();
|
||
FenceManager.RefreshAll();
|
||
|
||
var text = removed.Count == 1
|
||
? $"„{Storage.DisplayName(removed[0])}“ entfernt – das Programm ist nicht mehr installiert"
|
||
: $"{removed.Count} Verknüpfungen entfernt – die Programme sind nicht mehr installiert";
|
||
if (IsVisible && IsActive) ShowToast(text, "", undoable: true);
|
||
else App.TrayIcon?.Notify("Aufgeräumt", text);
|
||
}
|
||
finally
|
||
{
|
||
_deadScanRunning = false;
|
||
}
|
||
}
|
||
|
||
private void DeadShortcutsToggle_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
S.RemoveDeadShortcuts = DeadShortcutsToggle.IsChecked == true;
|
||
// beim Einschalten: früher behaltene Verknüpfungen wieder prüfen lassen
|
||
if (S.RemoveDeadShortcuts) S.KeptShortcuts.RemoveAll(p => !File.Exists(p));
|
||
S.Save();
|
||
if (S.RemoveDeadShortcuts) ScheduleDeadShortcutScan();
|
||
}
|
||
}
|