diff --git a/Penumbra/Collections/Manager/CollectionStorage.cs b/Penumbra/Collections/Manager/CollectionStorage.cs index e19acd35..de723729 100644 --- a/Penumbra/Collections/Manager/CollectionStorage.cs +++ b/Penumbra/Collections/Manager/CollectionStorage.cs @@ -194,12 +194,16 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer } /// Remove all settings for not currently-installed mods from the given collection. - public void CleanUnavailableSettings(ModCollection collection) + public int CleanUnavailableSettings(ModCollection collection) { - var any = collection.Settings.Unused.Count > 0; - ((Dictionary)collection.Settings.Unused).Clear(); - if (any) + var count = collection.Settings.Unused.Count; + if (count > 0) + { + ((Dictionary)collection.Settings.Unused).Clear(); _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); + } + + return count; } /// Remove a specific setting for not currently-installed mods from the given collection. diff --git a/Penumbra/Services/CleanupService.cs b/Penumbra/Services/CleanupService.cs new file mode 100644 index 00000000..490c2407 --- /dev/null +++ b/Penumbra/Services/CleanupService.cs @@ -0,0 +1,74 @@ +using OtterGui.Services; +using Penumbra.Collections.Manager; +using Penumbra.Mods.Manager; + +namespace Penumbra.Services; + +public class CleanupService(SaveService saveService, ModManager mods, CollectionManager collections) : IService +{ + public void CleanUnusedLocalData() + { + var usedFiles = mods.Select(saveService.FileNames.LocalDataFile).ToHashSet(); + foreach (var file in saveService.FileNames.LocalDataFiles.ToList()) + { + try + { + if (!file.Exists || usedFiles.Contains(file.FullName)) + continue; + + file.Delete(); + Penumbra.Log.Information($"[CleanupService] Deleted unused local data file {file.Name}."); + } + catch (Exception ex) + { + Penumbra.Log.Error($"[CleanupService] Failed to delete unused local data file {file.Name}:\n{ex}"); + } + } + } + + public void CleanBackupFiles() + { + foreach (var file in mods.BasePath.EnumerateFiles("group_*.json.bak", SearchOption.AllDirectories)) + { + try + { + if (!file.Exists) + continue; + + file.Delete(); + Penumbra.Log.Information($"[CleanupService] Deleted group backup file {file.FullName}."); + } + catch (Exception ex) + { + Penumbra.Log.Error($"[CleanupService] Failed to delete group backup file {file.FullName}:\n{ex}"); + } + } + + foreach (var file in Directory.EnumerateFiles(saveService.FileNames.ConfigDirectory, "*.json.bak", SearchOption.AllDirectories)) + { + try + { + if (!File.Exists(file)) + continue; + + File.Delete(file); + Penumbra.Log.Information($"[CleanupService] Deleted config backup file {file}."); + } + catch (Exception ex) + { + Penumbra.Log.Error($"[CleanupService] Failed to delete config backup file {file}:\n{ex}"); + } + } + } + + public void CleanupAllUnusedSettings() + { + foreach (var collection in collections.Storage) + { + var count = collections.Storage.CleanUnavailableSettings(collection); + if (count > 0) + Penumbra.Log.Information( + $"[CleanupService] Removed {count} unused settings from collection {collection.Identity.AnonymizedName}."); + } + } +} diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index c7f66859..e847b291 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -49,6 +49,7 @@ public class SettingsTab : ITab, IUiService private readonly CrashHandlerService _crashService; private readonly MigrationSectionDrawer _migrationDrawer; private readonly CollectionAutoSelector _autoSelector; + private readonly CleanupService _cleanupService; private int _minimumX = int.MaxValue; private int _minimumY = int.MaxValue; @@ -60,7 +61,7 @@ public class SettingsTab : ITab, IUiService CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, HttpApi httpApi, DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor, DalamudConfigService dalamudConfig, IDataManager gameData, PredefinedTagManager predefinedTagConfig, CrashHandlerService crashService, - MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector) + MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector, CleanupService cleanupService) { _pluginInterface = pluginInterface; _config = config; @@ -84,6 +85,7 @@ public class SettingsTab : ITab, IUiService _crashService = crashService; _migrationDrawer = migrationDrawer; _autoSelector = autoSelector; + _cleanupService = cleanupService; } public void DrawHeader() @@ -789,9 +791,13 @@ public class SettingsTab : ITab, IUiService DrawWaitForPluginsReflection(); DrawEnableHttpApiBox(); DrawEnableDebugModeBox(); + ImGui.Separator(); DrawReloadResourceButton(); DrawReloadFontsButton(); + ImGui.Separator(); + DrawCleanupButtons(); ImGui.NewLine(); + } private void DrawCrashHandler() @@ -982,6 +988,29 @@ public class SettingsTab : ITab, IUiService _fontReloader.Reload(); } + private void DrawCleanupButtons() + { + var enabled = _config.DeleteModModifier.IsActive(); + if (ImUtf8.ButtonEx("Clear Unused Local Mod Data Files"u8, + "Delete all local mod data files that do not correspond to currently installed mods."u8, default, !enabled)) + _cleanupService.CleanUnusedLocalData(); + if (!enabled) + ImUtf8.HoverTooltip($"Hold {_config.DeleteModModifier} while clicking to delete files."); + + if (ImUtf8.ButtonEx("Clear Backup Files"u8, + "Delete all backups of .json configuration files in your configuration folder and all backups of mod group files in your mod directory."u8, + default, !enabled)) + _cleanupService.CleanBackupFiles(); + if (!enabled) + ImUtf8.HoverTooltip($"Hold {_config.DeleteModModifier} while clicking to delete files."); + + if (ImUtf8.ButtonEx("Clear All Unused Settings"u8, + "Remove all mod settings in all of your collections that do not correspond to currently installed mods."u8, default, !enabled)) + _cleanupService.CleanupAllUnusedSettings(); + if (!enabled) + ImUtf8.HoverTooltip($"Hold {_config.DeleteModModifier} while clicking to remove settings."); + } + /// Draw a checkbox that toggles the dalamud setting to wait for plugins on open. private void DrawWaitForPluginsReflection() {