diff --git a/HellionChat/MessageStore.cs b/HellionChat/MessageStore.cs index 1ec4372..c46f841 100644 --- a/HellionChat/MessageStore.cs +++ b/HellionChat/MessageStore.cs @@ -529,6 +529,12 @@ internal class MessageStore : IDisposable var index = 0; foreach (var (type, days) in chatTypeDaysMap) { + // Careful: 0 here is NOT the "keep forever" it means for + // defaultDays below. A per-channel 0 puts the cutoff at now + // and deletes the channel's entire history. No profile ships + // a 0 and no UI can set one, which is why this is a comment + // and not a guard -- but any editor added later has to + // reconcile the two meanings before it exposes the value. var cutoff = nowMs - days * 86400000L; var typeParam = $"$type{index}"; var cutoffParam = $"$cutoff{index}"; diff --git a/HellionChat/Ui/Components/Settings/Tabs/DataPrivacyTab.cs b/HellionChat/Ui/Components/Settings/Tabs/DataPrivacyTab.cs index c8713a7..d7803bb 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/DataPrivacyTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/DataPrivacyTab.cs @@ -1,5 +1,7 @@ using Dalamud.Bindings.ImGui; using HellionChat.Code; +using HellionChat.Resources; +using HellionChat.Util; namespace HellionChat.Ui.Components.Settings.Tabs; @@ -19,17 +21,24 @@ internal sealed class DataPrivacyTab if (ImGui.CollapsingHeader("Logging", ImGuiTreeNodeFlags.DefaultOpen)) { _w.Toggle( - "Enable retention sweep", + HellionStrings.Retention_Enabled_Name, () => Plugin.Config.RetentionEnabled, v => Plugin.Config.RetentionEnabled = v ); + ImGuiUtil.HelpMarker(HellionStrings.Retention_Enabled_Description); + + // Down to 0, which DeleteByRetentionPolicy reads as "keep forever" + // for channels without an override. The slider started at 1, so the + // one value that means "never delete anything" was unreachable + // through the UI while the label promised it. _w.SliderInt( - "Default retention (days)", + HellionStrings.Retention_Default_Label, () => Plugin.Config.RetentionDefaultDays, v => Plugin.Config.RetentionDefaultDays = v, - 1, + 0, 365 ); + ImGuiUtil.HelpMarker(HellionStrings.Retention_Default_Help); // RetentionLastRunAt defaults to MinValue on a fresh install, which // would render as "0001-01-01 00:00" and look like a bug; the "Never" @@ -41,6 +50,8 @@ internal sealed class DataPrivacyTab ? "Never" : Plugin.Config.RetentionLastRunAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm"); ImGui.TextDisabled($"Last run: {lastRun}"); + + DrawRetentionOverrides(); } if (ImGui.CollapsingHeader("Privacy filter", ImGuiTreeNodeFlags.DefaultOpen)) @@ -66,6 +77,53 @@ internal sealed class DataPrivacyTab } } + // Read-only on purpose. Three of the four wizard profiles write a + // per-channel policy and switch the sweep on, and until now the window only + // showed the global default -- so whatever the wizard decided about your + // channels was invisible from here on. + // + // Editing them is a separate matter: 0 means "keep forever" as a global + // default but "delete everything" as a per-channel value, and no UI should + // offer that until the two agree. + private void DrawRetentionOverrides() + { + var overrides = Plugin.Config.RetentionPerChannelDays; + + ImGui.Spacing(); + ImGui.TextUnformatted(HellionStrings.Retention_Tree_Heading); + + var shown = 0; + foreach (var type in EnumValues.All) + { + var days = Plugin.Config.GetRetentionDays(type); + if (days == Plugin.Config.RetentionDefaultDays && !overrides.ContainsKey(type)) + continue; + + var tag = overrides.ContainsKey(type) + ? HellionStrings.Retention_Tag_Override + : HellionStrings.Retention_Tag_Spec; + ImGui.TextDisabled($" {type.Name()}: {days} d {tag}"); + shown++; + } + + if (shown == 0) + { + ImGui.TextDisabled($" {HellionStrings.Retention_Tag_Global}"); + return; + } + + if (overrides.Count > 0 && ImGui.Button(HellionStrings.Retention_Clear_Overrides)) + { + // Same lock the sweep takes when it snapshots the policy, so a clear + // cannot cut its enumeration short. + lock (_plugin.ConfigMapsLock) + { + overrides.Clear(); + } + _plugin.SaveConfig(); + } + } + private void DrawPrivacyPersistChannelsGrid() { // HashSet: iterate Enum.GetValues() for stable