using System.IO; using System.Windows.Threading; namespace DeskCore; /// /// Beobachtet Desktop und Kategorie-Ordner. Meldet Änderungen gebündelt (statt bei jedem einzelnen Ereignis) /// und sammelt neu auf dem Desktop aufgetauchte Elemente für die automatische Sortierung. /// public sealed class Watcher : IDisposable { private readonly List _watchers = []; private readonly DispatcherTimer _changedTimer; private readonly DispatcherTimer _newTimer; private readonly HashSet _newOnDesktop = new(StringComparer.OrdinalIgnoreCase); private readonly object _lock = new(); /// Irgendetwas hat sich geändert (UI-Thread). public event Action? Changed; /// Neue Elemente auf dem Desktop (UI-Thread, nach kurzer Wartezeit). public event Action>? NewOnDesktop; public Watcher(Dispatcher dispatcher) { _changedTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(350), DispatcherPriority.Background, (_, _) => { _changedTimer!.Stop(); Changed?.Invoke(); }, dispatcher); _changedTimer.Stop(); // Etwas länger warten: Installer legen Verknüpfungen oft in mehreren Schritten an _newTimer = new DispatcherTimer(TimeSpan.FromSeconds(2), DispatcherPriority.Background, (_, _) => { _newTimer!.Stop(); List items; lock (_lock) { items = _newOnDesktop.ToList(); _newOnDesktop.Clear(); } items = items.Where(p => (File.Exists(p) || Directory.Exists(p)) && !Storage.CreatedByApp(p)).ToList(); if (items.Count > 0) NewOnDesktop?.Invoke(items); }, dispatcher); _newTimer.Stop(); Directory.CreateDirectory(Storage.Root); Add(Storage.UserDesktop, false, true); Add(Storage.PublicDesktop, false, true); Add(Storage.Root, true, false); } private void Add(string dir, bool subdirs, bool isDesktop) { if (!Directory.Exists(dir)) return; try { var w = new FileSystemWatcher(dir) { IncludeSubdirectories = subdirs, NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite, InternalBufferSize = 64 * 1024, }; FileSystemEventHandler onChange = (_, e) => { Poke(); if (isDesktop && e.ChangeType == WatcherChangeTypes.Created) NoteNew(e.FullPath); }; w.Created += onChange; w.Deleted += onChange; w.Changed += onChange; w.Renamed += (_, e) => { Poke(); if (isDesktop) NoteNew(e.FullPath); }; w.Error += (_, _) => Poke(); w.EnableRaisingEvents = true; _watchers.Add(w); } catch { } } private void NoteNew(string path) { var name = Path.GetFileName(path); if (name.StartsWith('~') || name.EndsWith(".tmp", StringComparison.OrdinalIgnoreCase) || name.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase)) return; lock (_lock) _newOnDesktop.Add(path); _newTimer.Dispatcher.BeginInvoke(() => { _newTimer.Stop(); _newTimer.Start(); }); } private void Poke() => _changedTimer.Dispatcher.BeginInvoke(() => { _changedTimer.Stop(); _changedTimer.Start(); }); public void Dispose() { foreach (var w in _watchers) w.Dispose(); _changedTimer.Stop(); _newTimer.Stop(); } } public static class AutoSorter { /// Dateityp-Gruppen für Regeln vom Typ "Dateityp ist". private static readonly Dictionary Groups = new(StringComparer.CurrentCultureIgnoreCase) { ["Bilder"] = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "heic", "tif", "tiff", "svg", "ico", "raw", "psd"], ["Videos"] = ["mp4", "mkv", "avi", "mov", "wmv", "webm", "m4v", "flv", "mpg", "mpeg"], ["Musik"] = ["mp3", "wav", "flac", "aac", "ogg", "m4a", "wma", "opus"], ["Dokumente"] = ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "odt", "ods", "odp", "txt", "rtf", "md", "csv"], ["Archive"] = ["zip", "rar", "7z", "tar", "gz", "bz2", "xz", "iso"], ["Programme"] = ["exe", "msi", "lnk", "url", "appref-ms", "bat", "cmd"], ["Verknüpfungen"] = ["lnk", "url", "appref-ms"], }; /// Passt eine Regel auf das Element? public static bool Matches(SortRule r, string path) { var value = r.Contains.Trim(); if (value.Length == 0) return false; if (r.Kind != "Typ") return Storage.DisplayName(path).Contains(value, StringComparison.CurrentCultureIgnoreCase); bool isFolder = Directory.Exists(path); var ext = Path.GetExtension(path).TrimStart('.').ToLowerInvariant(); foreach (var token in value.Split([',', ';', ' '], StringSplitOptions.RemoveEmptyEntries)) { var t = token.Trim().TrimStart('*', '.').ToLowerInvariant(); if (t is "ordner") { if (isFolder) return true; continue; } if (Groups.TryGetValue(t, out var exts)) { if (!isFolder && exts.Contains(ext)) return true; continue; } if (!isFolder && t == ext) return true; } return false; } /// Erste passende Regel → Kategoriename (nur existierende Kategorien). public static string? Match(string path) { var cats = Storage.Categories(); foreach (var r in AppSettings.Current.Rules) { if (!Matches(r, path)) continue; var cat = cats.FirstOrDefault(c => string.Equals(c, r.Category, StringComparison.OrdinalIgnoreCase)); if (cat is not null) return cat; } return null; } }