using System.IO; using System.Windows; using System.Windows.Threading; namespace DeskCore; /// Verknüpfungen zu deinstallierten Programmen automatisch aus den Kategorien entfernen. public partial class MainWindow { private DispatcherTimer? _deadScanTimer; private DispatcherTimer? _deadScanPeriodic; private bool _deadScanRunning; private DispatcherTimer? _deadConfirmTimer; private readonly Dictionary _deadSince = new(StringComparer.OrdinalIgnoreCase); private static readonly TimeSpan ConfirmAfter = TimeSpan.FromSeconds(60); /// Zweite Prüfung kurz nach Ablauf der Wartezeit. 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(); } /// Einmal beim Start einrichten: Prüfung kurz nach dem Start und danach alle 10 Minuten. 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(); } /// Nach Änderungen (z. B. Deinstallation entfernt die Desktop-Verknüpfung) gebündelt prüfen. 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(); 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(); } }