Files
DeskCore/Usage.cs
T
2026-10-04 14:48:53 +00:00

121 lines
4.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using System.IO;
using System.Text.Json;
using System.Windows.Threading;
namespace DeskCore;
public class UsageEntry
{
public int Count { get; set; }
public DateTime Last { get; set; }
}
/// <summary>
/// Merkt sich, was wie oft und wann zuletzt über DeskCore gestartet wurde (für „Meistgenutzt“, „Zuletzt benutzt“
/// und die Schnellstart-Suche). Liegt getrennt von den Einstellungen in usage.json, weil es sich ständig ändert.
/// </summary>
public static class Usage
{
/// <summary>Schlüssel-Präfix für Start-Gruppen (die keinen Dateipfad haben).</summary>
public const string GroupPrefix = "gruppe:";
private static readonly string FilePath = Path.Combine(AppSettings.Folder, "usage.json");
private static readonly Dictionary<string, UsageEntry> _data = Load();
private static DispatcherTimer? _saveTimer;
/// <summary>Etwas wurde gestartet (z. B. damit „Zuletzt benutzt“-Boxen sich aktualisieren).</summary>
public static event Action? Changed;
private static Dictionary<string, UsageEntry> Load()
{
try
{
if (File.Exists(FilePath) &&
JsonSerializer.Deserialize<Dictionary<string, UsageEntry>>(File.ReadAllText(FilePath)) is { } d)
return new(d, StringComparer.OrdinalIgnoreCase);
}
catch { }
return new(StringComparer.OrdinalIgnoreCase);
}
/// <summary>Programm/Datei starten und den Start mitzählen. Wirft bei Fehlern wie Process.Start.</summary>
public static void Start(string path)
{
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
Record(path);
}
public static void Record(string key)
{
if (!_data.TryGetValue(key, out var e)) _data[key] = e = new UsageEntry();
e.Count++;
e.Last = DateTime.Now;
ScheduleSave();
Changed?.Invoke();
}
public static int Count(string key) => _data.TryGetValue(key, out var e) ? e.Count : 0;
/// <summary>
/// Wie „beliebt“ etwas ist: Anzahl der Starts, mit der Zeit abnehmend gewichtet –
/// was du früher oft, aber seit Wochen nicht mehr benutzt hast, rutscht langsam nach hinten.
/// </summary>
public static double Score(string key)
{
if (!_data.TryGetValue(key, out var e)) return 0;
double days = Math.Max(0, (DateTime.Now - e.Last).TotalDays);
return e.Count / (1 + days / 30);
}
/// <summary>Zuletzt gestartete Dateien, die es noch gibt (neueste zuerst).</summary>
public static List<string> Recent(int max) => _data
.Where(kv => !kv.Key.StartsWith(GroupPrefix) && (File.Exists(kv.Key) || Directory.Exists(kv.Key)))
.OrderByDescending(kv => kv.Value.Last)
.Take(max)
.Select(kv => kv.Key)
.ToList();
/// <summary>Datei wurde umbenannt/verschoben → Zählung mitnehmen.</summary>
public static void Rename(string oldKey, string newKey)
{
if (!_data.Remove(oldKey, out var e)) return;
if (_data.TryGetValue(newKey, out var existing))
{
existing.Count += e.Count;
if (e.Last > existing.Last) existing.Last = e.Last;
}
else _data[newKey] = e;
ScheduleSave();
}
public static void Forget(string key)
{
if (_data.Remove(key)) { ScheduleSave(); Changed?.Invoke(); }
}
private static void ScheduleSave()
{
_saveTimer ??= new DispatcherTimer(TimeSpan.FromSeconds(2), DispatcherPriority.Background, (_, _) => Flush(),
Dispatcher.CurrentDispatcher);
_saveTimer.Stop();
_saveTimer.Start();
}
/// <summary>Sofort speichern (z. B. beim Beenden).</summary>
public static void Flush()
{
_saveTimer?.Stop();
try
{
// Nicht mehr vorhandene Einträge, die lange nicht benutzt wurden, aufräumen
foreach (var key in _data.Where(kv => kv.Value.Last < DateTime.Now.AddDays(-180) && !kv.Key.StartsWith(GroupPrefix)
&& !File.Exists(kv.Key) && !Directory.Exists(kv.Key)).Select(kv => kv.Key).ToList())
_data.Remove(key);
Directory.CreateDirectory(AppSettings.Folder);
File.WriteAllText(FilePath, JsonSerializer.Serialize(_data));
}
catch { }
}
}