mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
tmp
This commit is contained in:
parent
2bf80dfa6b
commit
e86899c943
11 changed files with 362 additions and 353 deletions
|
|
@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
|
|||
using OtterGui;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
@ -14,11 +15,13 @@ public class CollectionEditor
|
|||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly ModStorage _modStorage;
|
||||
|
||||
public CollectionEditor(SaveService saveService, CommunicatorService communicator)
|
||||
public CollectionEditor(SaveService saveService, CommunicatorService communicator, ModStorage modStorage)
|
||||
{
|
||||
_saveService = saveService;
|
||||
_communicator = communicator;
|
||||
_modStorage = modStorage;
|
||||
}
|
||||
|
||||
/// <summary> Enable or disable the mod inheritance of mod idx. </summary>
|
||||
|
|
@ -37,12 +40,12 @@ public class CollectionEditor
|
|||
/// </summary>
|
||||
public bool SetModState(ModCollection collection, Mod mod, bool newValue)
|
||||
{
|
||||
var oldValue = collection._settings[mod.Index]?.Enabled ?? collection[mod.Index].Settings?.Enabled ?? false;
|
||||
var oldValue = collection.Settings[mod.Index]?.Enabled ?? collection[mod.Index].Settings?.Enabled ?? false;
|
||||
if (newValue == oldValue)
|
||||
return false;
|
||||
|
||||
var inheritance = FixInheritance(collection, mod, false);
|
||||
collection._settings[mod.Index]!.Enabled = newValue;
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index]!.Enabled = newValue;
|
||||
InvokeChange(collection, ModSettingChange.EnableState, mod, inheritance ? -1 : newValue ? 0 : 1, 0);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -65,13 +68,13 @@ public class CollectionEditor
|
|||
var changes = false;
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
var oldValue = collection._settings[mod.Index]?.Enabled;
|
||||
var oldValue = collection.Settings[mod.Index]?.Enabled;
|
||||
if (newValue == oldValue)
|
||||
continue;
|
||||
|
||||
FixInheritance(collection, mod, false);
|
||||
collection._settings[mod.Index]!.Enabled = newValue;
|
||||
changes = true;
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index]!.Enabled = newValue;
|
||||
changes = true;
|
||||
}
|
||||
|
||||
if (!changes)
|
||||
|
|
@ -86,12 +89,12 @@ public class CollectionEditor
|
|||
/// </summary>
|
||||
public bool SetModPriority(ModCollection collection, Mod mod, int newValue)
|
||||
{
|
||||
var oldValue = collection._settings[mod.Index]?.Priority ?? collection[mod.Index].Settings?.Priority ?? 0;
|
||||
var oldValue = collection.Settings[mod.Index]?.Priority ?? collection[mod.Index].Settings?.Priority ?? 0;
|
||||
if (newValue == oldValue)
|
||||
return false;
|
||||
|
||||
var inheritance = FixInheritance(collection, mod, false);
|
||||
collection._settings[mod.Index]!.Priority = newValue;
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index]!.Priority = newValue;
|
||||
InvokeChange(collection, ModSettingChange.Priority, mod, inheritance ? -1 : oldValue, 0);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -102,15 +105,15 @@ public class CollectionEditor
|
|||
/// </summary>
|
||||
public bool SetModSetting(ModCollection collection, Mod mod, int groupIdx, uint newValue)
|
||||
{
|
||||
var settings = collection._settings[mod.Index] != null
|
||||
? collection._settings[mod.Index]!.Settings
|
||||
var settings = collection.Settings[mod.Index] != null
|
||||
? collection.Settings[mod.Index]!.Settings
|
||||
: collection[mod.Index].Settings?.Settings;
|
||||
var oldValue = settings?[groupIdx] ?? mod.Groups[groupIdx].DefaultSettings;
|
||||
if (oldValue == newValue)
|
||||
return false;
|
||||
|
||||
var inheritance = FixInheritance(collection, mod, false);
|
||||
collection._settings[mod.Index]!.SetValue(mod, groupIdx, newValue);
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index]!.SetValue(mod, groupIdx, newValue);
|
||||
InvokeChange(collection, ModSettingChange.Setting, mod, inheritance ? -1 : (int)oldValue, groupIdx);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -125,10 +128,10 @@ public class CollectionEditor
|
|||
// If it does not exist, check unused settings.
|
||||
// If it does not exist and has no unused settings, also use null.
|
||||
ModSettings.SavedSettings? savedSettings = sourceMod != null
|
||||
? collection._settings[sourceMod.Index] != null
|
||||
? new ModSettings.SavedSettings(collection._settings[sourceMod.Index]!, sourceMod)
|
||||
? collection.Settings[sourceMod.Index] != null
|
||||
? new ModSettings.SavedSettings(collection.Settings[sourceMod.Index]!, sourceMod)
|
||||
: null
|
||||
: collection._unusedSettings.TryGetValue(sourceName, out var s)
|
||||
: collection.UnusedSettings.TryGetValue(sourceName, out var s)
|
||||
? s
|
||||
: null;
|
||||
|
||||
|
|
@ -157,9 +160,9 @@ public class CollectionEditor
|
|||
// Either copy the unused source settings directly if they are not inheriting,
|
||||
// or remove any unused settings for the target if they are inheriting.
|
||||
if (savedSettings != null)
|
||||
collection._unusedSettings[targetName] = savedSettings.Value;
|
||||
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings)[targetName] = savedSettings.Value;
|
||||
else
|
||||
collection._unusedSettings.Remove(targetName);
|
||||
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Remove(targetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -189,11 +192,12 @@ public class CollectionEditor
|
|||
/// </summary>
|
||||
private static bool FixInheritance(ModCollection collection, Mod mod, bool inherit)
|
||||
{
|
||||
var settings = collection._settings[mod.Index];
|
||||
var settings = collection.Settings[mod.Index];
|
||||
if (inherit == (settings == null))
|
||||
return false;
|
||||
|
||||
collection._settings[mod.Index] = inherit ? null : collection[mod.Index].Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod);
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index] =
|
||||
inherit ? null : collection[mod.Index].Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +205,7 @@ public class CollectionEditor
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void InvokeChange(ModCollection changedCollection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx)
|
||||
{
|
||||
_saveService.QueueSave(changedCollection);
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, changedCollection));
|
||||
_communicator.ModSettingChanged.Invoke(changedCollection, type, mod, oldValue, groupIdx, false);
|
||||
RecurseInheritors(changedCollection, type, mod, oldValue, groupIdx);
|
||||
}
|
||||
|
|
@ -219,7 +223,7 @@ public class CollectionEditor
|
|||
_communicator.ModSettingChanged.Invoke(directInheritor, type, null, oldValue, groupIdx, true);
|
||||
break;
|
||||
default:
|
||||
if (directInheritor._settings[mod!.Index] == null)
|
||||
if (directInheritor.Settings[mod!.Index] == null)
|
||||
_communicator.ModSettingChanged.Invoke(directInheritor, type, mod, oldValue, groupIdx, true);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,6 @@ public class CollectionManager
|
|||
Temp = temp;
|
||||
Editor = editor;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly ModStorage _modStorage;
|
||||
|
||||
/// <remarks> The empty collection is always available at Index 0. </remarks>
|
||||
private readonly List<ModCollection> _collections = new()
|
||||
|
|
@ -34,9 +35,6 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public IEnumerator<ModCollection> GetEnumeratorWithEmpty()
|
||||
=> _collections.GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> _collections.Count;
|
||||
|
||||
|
|
@ -53,10 +51,11 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
public CollectionStorage(CommunicatorService communicator, SaveService saveService)
|
||||
public CollectionStorage(CommunicatorService communicator, SaveService saveService, ModStorage modStorage)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_saveService = saveService;
|
||||
_modStorage = modStorage;
|
||||
_communicator.ModDiscoveryStarted.Subscribe(OnModDiscoveryStarted);
|
||||
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished);
|
||||
_communicator.ModPathChanged.Subscribe(OnModPathChange, 10);
|
||||
|
|
@ -114,21 +113,16 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
return false;
|
||||
}
|
||||
|
||||
var newCollection = duplicate?.Duplicate(name) ?? ModCollection.CreateNewEmpty(name);
|
||||
newCollection.Index = _collections.Count;
|
||||
var newCollection = duplicate?.Duplicate(name, _collections.Count) ?? ModCollection.CreateEmpty(name, _collections.Count);
|
||||
_collections.Add(newCollection);
|
||||
|
||||
_saveService.ImmediateSave(newCollection);
|
||||
_saveService.ImmediateSave(new ModCollectionSave(_modStorage, newCollection));
|
||||
Penumbra.ChatService.NotificationMessage($"Created new collection {newCollection.AnonymizedName}.", "Success",
|
||||
NotificationType.Success);
|
||||
_communicator.CollectionChange.Invoke(CollectionType.Inactive, null, newCollection, string.Empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Whether the given collection can be deleted. </summary>
|
||||
public bool CanRemoveCollection(ModCollection collection)
|
||||
=> collection.Index > ModCollection.Empty.Index && collection.Index < Count && collection.Index != DefaultNamed.Index;
|
||||
|
||||
/// <summary>
|
||||
/// Remove the given collection if it exists and is neither the empty nor the default-named collection.
|
||||
/// </summary>
|
||||
|
|
@ -146,7 +140,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
return false;
|
||||
}
|
||||
|
||||
_saveService.ImmediateDelete(collection);
|
||||
_saveService.ImmediateDelete(new ModCollectionSave(_modStorage, collection));
|
||||
_collections.RemoveAt(collection.Index);
|
||||
// Update indices.
|
||||
for (var i = collection.Index; i < Count; ++i)
|
||||
|
|
@ -192,6 +186,15 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary> Remove all settings for not currently-installed mods from the given collection. </summary>
|
||||
public void CleanUnavailableSettings(ModCollection collection)
|
||||
{
|
||||
var any = collection.UnusedSettings.Count > 0;
|
||||
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Clear();
|
||||
if (any)
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a name is valid to use for a collection.
|
||||
/// Does not check for uniqueness.
|
||||
|
|
@ -211,24 +214,23 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
|
||||
foreach (var file in _saveService.FileNames.CollectionFiles)
|
||||
{
|
||||
var collection = ModCollection.LoadFromFile(file, out var inheritance);
|
||||
if (collection == null || collection.Name.Length == 0)
|
||||
if (!ModCollectionSave.LoadFromFile(file, out var name, out var version, out var settings, out var inheritance) || !IsValidName(name))
|
||||
continue;
|
||||
|
||||
if (ByName(collection.Name, out _))
|
||||
if (ByName(name, out _))
|
||||
{
|
||||
Penumbra.ChatService.NotificationMessage($"Duplicate collection found: {collection.Name} already exists. Import skipped.",
|
||||
Penumbra.ChatService.NotificationMessage($"Duplicate collection found: {name} already exists. Import skipped.",
|
||||
"Warning", NotificationType.Warning);
|
||||
continue;
|
||||
}
|
||||
|
||||
var collection = ModCollection.CreateFromData(_saveService, _modStorage, name, version, Count, settings);
|
||||
var correctName = _saveService.FileNames.CollectionFile(collection);
|
||||
if (file.FullName != correctName)
|
||||
Penumbra.ChatService.NotificationMessage($"Collection {file.Name} does not correspond to {collection.Name}.", "Warning",
|
||||
NotificationType.Warning);
|
||||
|
||||
_inheritancesByName?.Add(inheritance);
|
||||
collection.Index = _collections.Count;
|
||||
_collections.Add(collection);
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +260,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
private void OnModDiscoveryStarted()
|
||||
{
|
||||
foreach (var collection in this)
|
||||
collection.PrepareModDiscovery();
|
||||
collection.PrepareModDiscovery(_modStorage);
|
||||
}
|
||||
|
||||
/// <summary> Restore all settings in all collections to mods. </summary>
|
||||
|
|
@ -266,7 +268,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
{
|
||||
// Re-apply all mod settings.
|
||||
foreach (var collection in this)
|
||||
collection.ApplyModSettings();
|
||||
collection.ApplyModSettings(_saveService, _modStorage);
|
||||
}
|
||||
|
||||
/// <summary> Add or remove a mod from all collections, or re-save all collections where the mod has settings. </summary>
|
||||
|
|
@ -281,11 +283,11 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
break;
|
||||
case ModPathChangeType.Deleted:
|
||||
foreach (var collection in this)
|
||||
collection.RemoveMod(mod, mod.Index);
|
||||
collection.RemoveMod(mod);
|
||||
break;
|
||||
case ModPathChangeType.Moved:
|
||||
foreach (var collection in this.Where(collection => collection.Settings[mod.Index] != null))
|
||||
_saveService.QueueSave(collection);
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -299,8 +301,8 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
|
||||
foreach (var collection in this)
|
||||
{
|
||||
if (collection._settings[mod.Index]?.HandleChanges(type, mod, groupIdx, optionIdx, movedToIdx) ?? false)
|
||||
_saveService.QueueSave(collection);
|
||||
if (collection.Settings[mod.Index]?.HandleChanges(type, mod, groupIdx, optionIdx, movedToIdx) ?? false)
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using OtterGui;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
@ -19,12 +20,16 @@ public class InheritanceManager : IDisposable
|
|||
public enum ValidInheritance
|
||||
{
|
||||
Valid,
|
||||
|
||||
/// <summary> Can not inherit from self </summary>
|
||||
Self,
|
||||
|
||||
/// <summary> Can not inherit from the empty collection </summary>
|
||||
Empty,
|
||||
|
||||
/// <summary> Already inherited from </summary>
|
||||
Contained,
|
||||
|
||||
/// <summary> Inheritance would lead to a circle. </summary>
|
||||
Circle,
|
||||
}
|
||||
|
|
@ -32,12 +37,14 @@ public class InheritanceManager : IDisposable
|
|||
private readonly CollectionStorage _storage;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly ModStorage _modStorage;
|
||||
|
||||
public InheritanceManager(CollectionStorage storage, SaveService saveService, CommunicatorService communicator)
|
||||
public InheritanceManager(CollectionStorage storage, SaveService saveService, CommunicatorService communicator, ModStorage modStorage)
|
||||
{
|
||||
_storage = storage;
|
||||
_saveService = saveService;
|
||||
_communicator = communicator;
|
||||
_modStorage = modStorage;
|
||||
|
||||
ApplyInheritances();
|
||||
_communicator.CollectionChange.Subscribe(OnCollectionChange);
|
||||
|
|
@ -80,6 +87,7 @@ public class InheritanceManager : IDisposable
|
|||
var parent = inheritor.DirectlyInheritsFrom[idx];
|
||||
((List<ModCollection>)inheritor.DirectlyInheritsFrom).RemoveAt(idx);
|
||||
((List<ModCollection>)parent.DirectParentOf).Remove(inheritor);
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
|
||||
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
|
||||
RecurseInheritanceChanges(inheritor);
|
||||
Penumbra.Log.Debug($"Removed {parent.AnonymizedName} from {inheritor.AnonymizedName} inheritances.");
|
||||
|
|
@ -91,6 +99,7 @@ public class InheritanceManager : IDisposable
|
|||
if (!((List<ModCollection>)inheritor.DirectlyInheritsFrom).Move(from, to))
|
||||
return;
|
||||
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
|
||||
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
|
||||
RecurseInheritanceChanges(inheritor);
|
||||
Penumbra.Log.Debug($"Moved {inheritor.AnonymizedName}s inheritance {from} to {to}.");
|
||||
|
|
@ -106,6 +115,7 @@ public class InheritanceManager : IDisposable
|
|||
((List<ModCollection>)parent.DirectParentOf).Add(inheritor);
|
||||
if (invokeEvent)
|
||||
{
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
|
||||
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
|
||||
RecurseInheritanceChanges(inheritor);
|
||||
}
|
||||
|
|
@ -134,7 +144,7 @@ public class InheritanceManager : IDisposable
|
|||
}
|
||||
|
||||
if (localChanges)
|
||||
_saveService.ImmediateSave(collection);
|
||||
_saveService.ImmediateSave(new ModCollectionSave(_modStorage, collection));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public class TempCollectionManager : IDisposable
|
|||
|
||||
if (GlobalChangeCounter == int.MaxValue)
|
||||
GlobalChangeCounter = 0;
|
||||
var collection = ModCollection.CreateNewTemporary(name, GlobalChangeCounter++);
|
||||
var collection = ModCollection.CreateTemporary(name, ~Count, GlobalChangeCounter++);
|
||||
if (_customCollections.TryAdd(collection.Name.ToLowerInvariant(), collection))
|
||||
return collection.Name;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,49 +6,30 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
// File operations like saving, loading and deleting for a collection.
|
||||
public partial class ModCollection : ISavable
|
||||
/// <summary>
|
||||
/// Handle saving and loading a collection.
|
||||
/// </summary>
|
||||
internal readonly struct ModCollectionSave : ISavable
|
||||
{
|
||||
// Since inheritances depend on other collections existing,
|
||||
// we return them as a list to be applied after reading all collections.
|
||||
internal static ModCollection? LoadFromFile(FileInfo file, out IReadOnlyList<string> inheritance)
|
||||
private readonly ModStorage _modStorage;
|
||||
private readonly ModCollection _modCollection;
|
||||
|
||||
public ModCollectionSave(ModStorage modStorage, ModCollection modCollection)
|
||||
{
|
||||
inheritance = Array.Empty<string>();
|
||||
if (!file.Exists)
|
||||
{
|
||||
Penumbra.Log.Error("Could not read collection because file does not exist.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var obj = JObject.Parse(File.ReadAllText(file.FullName));
|
||||
var name = obj[nameof(Name)]?.ToObject<string>() ?? string.Empty;
|
||||
var version = obj[nameof(Version)]?.ToObject<int>() ?? 0;
|
||||
// Custom deserialization that is converted with the constructor.
|
||||
var settings = obj[nameof(Settings)]?.ToObject<Dictionary<string, ModSettings.SavedSettings>>()
|
||||
?? new Dictionary<string, ModSettings.SavedSettings>();
|
||||
inheritance = obj["Inheritance"]?.ToObject<List<string>>() ?? (IReadOnlyList<string>)Array.Empty<string>();
|
||||
|
||||
return new ModCollection(name, version, settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not read collection information from file:\n{e}");
|
||||
}
|
||||
|
||||
return null;
|
||||
_modStorage = modStorage;
|
||||
_modCollection = modCollection;
|
||||
}
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.CollectionFile(this);
|
||||
=> fileNames.CollectionFile(_modCollection);
|
||||
|
||||
public string LogName(string _)
|
||||
=> AnonymizedName;
|
||||
=> _modCollection.AnonymizedName;
|
||||
|
||||
public string TypeName
|
||||
=> "Collection";
|
||||
|
|
@ -59,25 +40,25 @@ public partial class ModCollection : ISavable
|
|||
j.Formatting = Formatting.Indented;
|
||||
var x = JsonSerializer.Create(new JsonSerializerSettings { Formatting = Formatting.Indented });
|
||||
j.WriteStartObject();
|
||||
j.WritePropertyName(nameof(Version));
|
||||
j.WriteValue(Version);
|
||||
j.WritePropertyName(nameof(Name));
|
||||
j.WriteValue(Name);
|
||||
j.WritePropertyName(nameof(Settings));
|
||||
j.WritePropertyName("Version");
|
||||
j.WriteValue(ModCollection.CurrentVersion);
|
||||
j.WritePropertyName(nameof(ModCollection.Name));
|
||||
j.WriteValue(_modCollection.Name);
|
||||
j.WritePropertyName(nameof(ModCollection.Settings));
|
||||
|
||||
// Write all used and unused settings by mod directory name.
|
||||
j.WriteStartObject();
|
||||
for (var i = 0; i < _settings.Count; ++i)
|
||||
for (var i = 0; i < _modCollection.Settings.Count; ++i)
|
||||
{
|
||||
var settings = _settings[i];
|
||||
var settings = _modCollection.Settings[i];
|
||||
if (settings != null)
|
||||
{
|
||||
j.WritePropertyName(Penumbra.ModManager[i].ModPath.Name);
|
||||
x.Serialize(j, new ModSettings.SavedSettings(settings, Penumbra.ModManager[i]));
|
||||
j.WritePropertyName(_modStorage[i].ModPath.Name);
|
||||
x.Serialize(j, new ModSettings.SavedSettings(settings, _modStorage[i]));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (modDir, settings) in _unusedSettings)
|
||||
foreach (var (modDir, settings) in _modCollection.UnusedSettings)
|
||||
{
|
||||
j.WritePropertyName(modDir);
|
||||
x.Serialize(j, settings);
|
||||
|
|
@ -87,7 +68,40 @@ public partial class ModCollection : ISavable
|
|||
|
||||
// Inherit by collection name.
|
||||
j.WritePropertyName("Inheritance");
|
||||
x.Serialize(j, DirectlyInheritsFrom.Select(c => c.Name));
|
||||
x.Serialize(j, _modCollection.DirectlyInheritsFrom.Select(c => c.Name));
|
||||
j.WriteEndObject();
|
||||
}
|
||||
|
||||
public static bool LoadFromFile(FileInfo file, out string name, out int version, out Dictionary<string, ModSettings.SavedSettings> settings,
|
||||
out IReadOnlyList<string> inheritance)
|
||||
{
|
||||
settings = new Dictionary<string, ModSettings.SavedSettings>();
|
||||
inheritance = Array.Empty<string>();
|
||||
if (!file.Exists)
|
||||
{
|
||||
Penumbra.Log.Error("Could not read collection because file does not exist.");
|
||||
name = string.Empty;
|
||||
|
||||
version = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var obj = JObject.Parse(File.ReadAllText(file.FullName));
|
||||
name = obj[nameof(ModCollection.Name)]?.ToObject<string>() ?? string.Empty;
|
||||
version = obj["Version"]?.ToObject<int>() ?? 0;
|
||||
// Custom deserialization that is converted with the constructor.
|
||||
settings = obj[nameof(ModCollection.Settings)]?.ToObject<Dictionary<string, ModSettings.SavedSettings>>() ?? settings;
|
||||
inheritance = obj["Inheritance"]?.ToObject<List<string>>() ?? inheritance;
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
name = string.Empty;
|
||||
version = 0;
|
||||
Penumbra.Log.Error($"Could not read collection information from file:\n{e}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,48 @@
|
|||
using Penumbra.Mods;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public sealed partial class ModCollection
|
||||
/// <summary> Migration to convert ModCollections from older versions to newer. </summary>
|
||||
internal static class ModCollectionMigration
|
||||
{
|
||||
// Migration to convert ModCollections from older versions to newer.
|
||||
private static class Migration
|
||||
/// <summary> Migrate a mod collection to the current version. </summary>
|
||||
public static void Migrate(SaveService saver, ModStorage mods, int version, ModCollection collection)
|
||||
{
|
||||
var changes = MigrateV0ToV1(collection, ref version);
|
||||
if (changes)
|
||||
saver.ImmediateSave(new ModCollectionSave(mods, collection));
|
||||
}
|
||||
|
||||
/// <summary> Migrate a mod collection from Version 0 to Version 1, which introduced support for inheritance. </summary>
|
||||
private static bool MigrateV0ToV1(ModCollection collection, ref int version)
|
||||
{
|
||||
public static void Migrate(SaveService saver, ModCollection collection )
|
||||
if (version > 0)
|
||||
return false;
|
||||
|
||||
version = 1;
|
||||
|
||||
// Remove all completely defaulted settings from active and inactive mods.
|
||||
for (var i = 0; i < collection.Settings.Count; ++i)
|
||||
{
|
||||
var changes = MigrateV0ToV1( collection );
|
||||
if( changes )
|
||||
{
|
||||
saver.ImmediateSave(collection);
|
||||
}
|
||||
if (SettingIsDefaultV0(collection.Settings[i]))
|
||||
((List<ModSettings?>)collection.Settings)[i] = null;
|
||||
}
|
||||
|
||||
private static bool MigrateV0ToV1( ModCollection collection )
|
||||
{
|
||||
if( collection.Version > 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var (key, _) in collection.UnusedSettings.Where(kvp => SettingIsDefaultV0(kvp.Value)).ToList())
|
||||
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Remove(key);
|
||||
|
||||
collection.Version = 1;
|
||||
|
||||
// Remove all completely defaulted settings from active and inactive mods.
|
||||
for( var i = 0; i < collection._settings.Count; ++i )
|
||||
{
|
||||
if( SettingIsDefaultV0( collection._settings[ i ] ) )
|
||||
{
|
||||
collection._settings[ i ] = null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var (key, _) in collection._unusedSettings.Where( kvp => SettingIsDefaultV0( kvp.Value ) ).ToList() )
|
||||
{
|
||||
collection._unusedSettings.Remove( key );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// We treat every completely defaulted setting as inheritance-ready.
|
||||
private static bool SettingIsDefaultV0( ModSettings.SavedSettings setting )
|
||||
=> setting is { Enabled: false, Priority: 0 } && setting.Settings.Values.All( s => s == 0 );
|
||||
|
||||
private static bool SettingIsDefaultV0( ModSettings? setting )
|
||||
=> setting is { Enabled: false, Priority: 0 } && setting.Settings.All( s => s == 0 );
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static ModCollection MigrateFromV0( string name, Dictionary< string, ModSettings.SavedSettings > allSettings )
|
||||
=> new(name, 0, allSettings);
|
||||
}
|
||||
/// <summary> We treat every completely defaulted setting as inheritance-ready. </summary>
|
||||
private static bool SettingIsDefaultV0(ModSettings.SavedSettings setting)
|
||||
=> setting is { Enabled: false, Priority: 0 } && setting.Settings.Values.All(s => s == 0);
|
||||
|
||||
/// <inheritdoc cref="SettingIsDefaultV0(ModSettings.SavedSettings)"/>
|
||||
private static bool SettingIsDefaultV0(ModSettings? setting)
|
||||
=> setting is { Enabled: false, Priority: 0 } && setting.Settings.All(s => s == 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,168 +1,73 @@
|
|||
using OtterGui.Filesystem;
|
||||
using Penumbra.Mods;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using OtterGui;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
// A ModCollection is a named set of ModSettings to all of the users' installed mods.
|
||||
// Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made.
|
||||
// Invariants:
|
||||
// - Index is the collections index in the ModCollection.Manager
|
||||
// - Settings has the same size as ModManager.Mods.
|
||||
// - any change in settings or inheritance of the collection causes a Save.
|
||||
/// <summary>
|
||||
/// A ModCollection is a named set of ModSettings to all of the users' installed mods.
|
||||
/// Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made.
|
||||
/// Invariants:
|
||||
/// - Index is the collections index in the ModCollection.Manager
|
||||
/// - Settings has the same size as ModManager.Mods.
|
||||
/// - any change in settings or inheritance of the collection causes a Save.
|
||||
/// - the name can not contain invalid path characters and has to be unique when lower-cased.
|
||||
/// </summary>
|
||||
public partial class ModCollection
|
||||
{
|
||||
public const int CurrentVersion = 1;
|
||||
public const string DefaultCollectionName = "Default";
|
||||
public const string EmptyCollection = "None";
|
||||
public const string EmptyCollectionName = "None";
|
||||
|
||||
public static readonly ModCollection Empty = CreateEmpty();
|
||||
/// <summary>
|
||||
/// Create the always available Empty Collection that will always sit at index 0,
|
||||
/// can not be deleted and does never create a cache.
|
||||
/// </summary>
|
||||
public static readonly ModCollection Empty = CreateEmpty(EmptyCollectionName, 0);
|
||||
|
||||
// The collection name can contain invalid path characters,
|
||||
// but after removing those and going to lower case it has to be unique.
|
||||
/// <summary> The name of a collection can not contain characters invalid in a path. </summary>
|
||||
public string Name { get; internal init; }
|
||||
|
||||
// Get the first two letters of a collection name and its Index (or None if it is the empty collection).
|
||||
public string AnonymizedName
|
||||
=> this == Empty ? Empty.Name : Name.Length > 2 ? $"{Name[..2]}... ({Index})" : $"{Name} ({Index})";
|
||||
|
||||
public int Version { get; internal set; }
|
||||
public int Index { get; internal set; } = -1;
|
||||
|
||||
// If a ModSetting is null, it can be inherited from other collections.
|
||||
// If no collection provides a setting for the mod, it is just disabled.
|
||||
internal readonly List<ModSettings?> _settings;
|
||||
|
||||
public IReadOnlyList<ModSettings?> Settings
|
||||
=> _settings;
|
||||
|
||||
// Returns whether there are settings not in use by any current mod.
|
||||
public bool HasUnusedSettings
|
||||
=> _unusedSettings.Count > 0;
|
||||
|
||||
public int NumUnusedSettings
|
||||
=> _unusedSettings.Count;
|
||||
|
||||
// Evaluates the settings along the whole inheritance tree.
|
||||
public IEnumerable<ModSettings?> ActualSettings
|
||||
=> Enumerable.Range(0, _settings.Count).Select(i => this[i].Settings);
|
||||
|
||||
// Settings for deleted mods will be kept via directory name.
|
||||
internal readonly Dictionary<string, ModSettings.SavedSettings> _unusedSettings;
|
||||
|
||||
// Constructor for duplication.
|
||||
private ModCollection(string name, ModCollection duplicate)
|
||||
{
|
||||
Name = name;
|
||||
Version = duplicate.Version;
|
||||
_settings = duplicate._settings.ConvertAll(s => s?.DeepCopy());
|
||||
_unusedSettings = duplicate._unusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy());
|
||||
DirectlyInheritsFrom = duplicate.DirectlyInheritsFrom.ToList();
|
||||
foreach (var c in DirectlyInheritsFrom)
|
||||
((List<ModCollection>)c.DirectParentOf).Add(this);
|
||||
}
|
||||
|
||||
// Constructor for reading from files.
|
||||
private ModCollection(string name, int version, Dictionary<string, ModSettings.SavedSettings> allSettings)
|
||||
{
|
||||
Name = name;
|
||||
Version = version;
|
||||
_unusedSettings = allSettings;
|
||||
|
||||
_settings = new List<ModSettings?>();
|
||||
ApplyModSettings();
|
||||
|
||||
Migration.Migrate(Penumbra.SaveService, this);
|
||||
}
|
||||
|
||||
// Create a new, unique empty collection of a given name.
|
||||
public static ModCollection CreateNewEmpty(string name)
|
||||
=> new(name, CurrentVersion, new Dictionary<string, ModSettings.SavedSettings>());
|
||||
|
||||
// Create a new temporary collection that does not save and has a negative index.
|
||||
public static ModCollection CreateNewTemporary(string name, int changeCounter)
|
||||
{
|
||||
var collection = new ModCollection(name, Empty)
|
||||
{
|
||||
Index = ~Penumbra.TempCollections.Count,
|
||||
ChangeCounter = changeCounter,
|
||||
};
|
||||
collection.CreateCache(false);
|
||||
return collection;
|
||||
}
|
||||
|
||||
// Duplicate the calling collection to a new, unique collection of a given name.
|
||||
public ModCollection Duplicate(string name)
|
||||
=> new(name, this);
|
||||
|
||||
// Remove all settings for not currently-installed mods.
|
||||
public void CleanUnavailableSettings()
|
||||
{
|
||||
var any = _unusedSettings.Count > 0;
|
||||
_unusedSettings.Clear();
|
||||
if (any)
|
||||
Penumbra.SaveService.QueueSave(this);
|
||||
}
|
||||
|
||||
// Add settings for a new appended mod, by checking if the mod had settings from a previous deletion.
|
||||
internal bool AddMod(Mod mod)
|
||||
{
|
||||
if (_unusedSettings.TryGetValue(mod.ModPath.Name, out var save))
|
||||
{
|
||||
var ret = save.ToSettings(mod, out var settings);
|
||||
_settings.Add(settings);
|
||||
_unusedSettings.Remove(mod.ModPath.Name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
_settings.Add(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move settings from the current mod list to the unused mod settings.
|
||||
internal void RemoveMod(Mod mod, int idx)
|
||||
{
|
||||
var settings = _settings[idx];
|
||||
if (settings != null)
|
||||
_unusedSettings[mod.ModPath.Name] = new ModSettings.SavedSettings(settings, mod);
|
||||
|
||||
_settings.RemoveAt(idx);
|
||||
}
|
||||
|
||||
// Create the always available Empty Collection that will always sit at index 0,
|
||||
// can not be deleted and does never create a cache.
|
||||
private static ModCollection CreateEmpty()
|
||||
{
|
||||
var collection = CreateNewEmpty(EmptyCollection);
|
||||
collection.Index = 0;
|
||||
collection._settings.Clear();
|
||||
return collection;
|
||||
}
|
||||
|
||||
// Move all settings to unused settings for rediscovery.
|
||||
internal void PrepareModDiscovery()
|
||||
{
|
||||
foreach (var (mod, setting) in Penumbra.ModManager.Zip(_settings).Where(s => s.Second != null))
|
||||
_unusedSettings[mod.ModPath.Name] = new ModSettings.SavedSettings(setting!, mod);
|
||||
|
||||
_settings.Clear();
|
||||
}
|
||||
|
||||
// Apply all mod settings from unused settings to the current set of mods.
|
||||
// Also fixes invalid settings.
|
||||
internal void ApplyModSettings()
|
||||
{
|
||||
_settings.Capacity = Math.Max(_settings.Capacity, Penumbra.ModManager.Count);
|
||||
if (Penumbra.ModManager.Aggregate(false, (current, mod) => current | AddMod(mod)))
|
||||
Penumbra.SaveService.ImmediateSave(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
|
||||
/// <summary> Get the first two letters of a collection name and its Index (or None if it is the empty collection). </summary>
|
||||
public string AnonymizedName
|
||||
=> this == Empty ? Empty.Name : Name.Length > 2 ? $"{Name[..2]}... ({Index})" : $"{Name} ({Index})";
|
||||
|
||||
/// <summary> The index of the collection is set and kept up-to-date by the CollectionManager. </summary>
|
||||
public int Index { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If a ModSetting is null, it can be inherited from other collections.
|
||||
/// If no collection provides a setting for the mod, it is just disabled.
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<ModSettings?> Settings;
|
||||
|
||||
/// <summary> Settings for deleted mods will be kept via the mods identifier (directory name). </summary>
|
||||
public readonly IReadOnlyDictionary<string, ModSettings.SavedSettings> UnusedSettings;
|
||||
|
||||
/// <summary> Contains all direct parent collections this collection inherits settings from. </summary>
|
||||
public readonly IReadOnlyList<ModCollection> DirectlyInheritsFrom;
|
||||
|
||||
/// <summary> Contains all direct child collections that inherit from this collection. </summary>
|
||||
public readonly IReadOnlyList<ModCollection> DirectParentOf = new List<ModCollection>();
|
||||
|
||||
/// <summary> All inherited collections in application order without filtering for duplicates. </summary>
|
||||
public static IEnumerable<ModCollection> InheritedCollections(ModCollection collection)
|
||||
=> collection.DirectlyInheritsFrom.SelectMany(InheritedCollections).Prepend(collection);
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over all collections inherited from in depth-first order.
|
||||
/// Skip already visited collections to avoid circular dependencies.
|
||||
/// </summary>
|
||||
public IEnumerable<ModCollection> GetFlattenedInheritance()
|
||||
=> InheritedCollections(this).Distinct();
|
||||
|
||||
/// <summary>
|
||||
/// Obtain the actual settings for a given mod via index.
|
||||
/// Also returns the collection the settings are taken from.
|
||||
|
|
@ -177,7 +82,7 @@ public partial class ModCollection
|
|||
|
||||
foreach (var collection in GetFlattenedInheritance())
|
||||
{
|
||||
var settings = collection._settings[idx];
|
||||
var settings = collection.Settings[idx];
|
||||
if (settings != null)
|
||||
return (settings, collection);
|
||||
}
|
||||
|
|
@ -186,17 +91,104 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
public readonly IReadOnlyList<ModCollection> DirectlyInheritsFrom = new List<ModCollection>();
|
||||
public readonly IReadOnlyList<ModCollection> DirectParentOf = new List<ModCollection>();
|
||||
|
||||
/// <summary> All inherited collections in application order without filtering for duplicates. </summary>
|
||||
public static IEnumerable<ModCollection> InheritedCollections(ModCollection collection)
|
||||
=> collection.DirectlyInheritsFrom.SelectMany(InheritedCollections).Prepend(collection);
|
||||
/// <summary> Evaluates all settings along the whole inheritance tree. </summary>
|
||||
public IEnumerable<ModSettings?> ActualSettings
|
||||
=> Enumerable.Range(0, Settings.Count).Select(i => this[i].Settings);
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over all collections inherited from in depth-first order.
|
||||
/// Skip already visited collections to avoid circular dependencies.
|
||||
/// Constructor for duplication. Deep copies all settings and parent collections and adds the new collection to their children lists.
|
||||
/// </summary>
|
||||
public IEnumerable<ModCollection> GetFlattenedInheritance()
|
||||
=> InheritedCollections(this).Distinct();
|
||||
public ModCollection Duplicate(string name, int index)
|
||||
{
|
||||
Debug.Assert(index > 0, "Collection duplicated with non-positive index.");
|
||||
return new ModCollection(name, index, 0, CurrentVersion, Settings.Select(s => s?.DeepCopy()).ToList(),
|
||||
DirectlyInheritsFrom.ToList(), UnusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy()));
|
||||
}
|
||||
|
||||
/// <summary> Constructor for reading from files. </summary>
|
||||
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, string name, int version, int index,
|
||||
Dictionary<string, ModSettings.SavedSettings> allSettings)
|
||||
{
|
||||
Debug.Assert(index > 0, "Collection read with non-positive index.");
|
||||
var ret = new ModCollection(name, index, 0, version, new List<ModSettings?>(), new List<ModCollection>(), allSettings);
|
||||
ret.ApplyModSettings(saver, mods);
|
||||
ModCollectionMigration.Migrate(saver, mods, version, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Constructor for temporary collections. </summary>
|
||||
public static ModCollection CreateTemporary(string name, int index, int changeCounter)
|
||||
{
|
||||
Debug.Assert(index < 0, "Temporary collection created with non-negative index.");
|
||||
var ret = new ModCollection(name, index, changeCounter, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(),
|
||||
new Dictionary<string, ModSettings.SavedSettings>());
|
||||
ret.CreateCache(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Constructor for empty collections. </summary>
|
||||
public static ModCollection CreateEmpty(string name, int index)
|
||||
{
|
||||
Debug.Assert(index >= 0, "Empty collection created with negative index.");
|
||||
return new ModCollection(name, index, 0, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(),
|
||||
new Dictionary<string, ModSettings.SavedSettings>());
|
||||
}
|
||||
|
||||
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
|
||||
internal bool AddMod(Mod mod)
|
||||
{
|
||||
if (UnusedSettings.TryGetValue(mod.ModPath.Name, out var save))
|
||||
{
|
||||
var ret = save.ToSettings(mod, out var settings);
|
||||
((List<ModSettings?>)Settings).Add(settings);
|
||||
((Dictionary<string, ModSettings.SavedSettings>)UnusedSettings).Remove(mod.ModPath.Name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
((List<ModSettings?>)Settings).Add(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary> Move settings from the current mod list to the unused mod settings. </summary>
|
||||
internal void RemoveMod(Mod mod)
|
||||
{
|
||||
var settings = Settings[mod.Index];
|
||||
if (settings != null)
|
||||
((Dictionary<string, ModSettings.SavedSettings>)UnusedSettings)[mod.ModPath.Name] = new ModSettings.SavedSettings(settings, mod);
|
||||
|
||||
((List<ModSettings?>)Settings).RemoveAt(mod.Index);
|
||||
}
|
||||
|
||||
/// <summary> Move all settings to unused settings for rediscovery. </summary>
|
||||
internal void PrepareModDiscovery(ModStorage mods)
|
||||
{
|
||||
foreach (var (mod, setting) in mods.Zip(Settings).Where(s => s.Second != null))
|
||||
((Dictionary<string, ModSettings.SavedSettings>)UnusedSettings)[mod.ModPath.Name] = new ModSettings.SavedSettings(setting!, mod);
|
||||
|
||||
((List<ModSettings?>)Settings).Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply all mod settings from unused settings to the current set of mods.
|
||||
/// Also fixes invalid settings.
|
||||
/// </summary>
|
||||
internal void ApplyModSettings(SaveService saver, ModStorage mods)
|
||||
{
|
||||
((List<ModSettings?>)Settings).Capacity = Math.Max(((List<ModSettings?>)Settings).Capacity, mods.Count);
|
||||
if (mods.Aggregate(false, (current, mod) => current | AddMod(mod)))
|
||||
saver.ImmediateSave(new ModCollectionSave(mods, this));
|
||||
}
|
||||
|
||||
private ModCollection(string name, int index, int changeCounter, int version, List<ModSettings?> appliedSettings,
|
||||
List<ModCollection> inheritsFrom, Dictionary<string, ModSettings.SavedSettings> settings)
|
||||
{
|
||||
Name = name;
|
||||
Index = index;
|
||||
ChangeCounter = changeCounter;
|
||||
Settings = appliedSettings;
|
||||
UnusedSettings = settings;
|
||||
DirectlyInheritsFrom = inheritsFrom;
|
||||
foreach (var c in DirectlyInheritsFrom)
|
||||
((List<ModCollection>)c.DirectParentOf).Add(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,8 @@ public class PenumbraNew
|
|||
.AddSingleton<ModExportManager>()
|
||||
.AddSingleton<ModImportManager>()
|
||||
.AddSingleton<ModFileSystem>()
|
||||
.AddSingleton<ModCacheManager>();
|
||||
.AddSingleton<ModCacheManager>()
|
||||
.AddSingleton(s => (ModStorage) s.GetRequiredService<ModManager>());
|
||||
|
||||
// Add Resource services
|
||||
services.AddSingleton<ResourceLoader>()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ using OtterGui.Filesystem;
|
|||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.Util;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace Penumbra.Services;
|
||||
|
|
@ -20,40 +22,32 @@ namespace Penumbra.Services;
|
|||
/// </summary>
|
||||
public class ConfigMigrationService
|
||||
{
|
||||
private readonly FilenameService _fileNames;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly SaveService _saveService;
|
||||
|
||||
private Configuration _config = null!;
|
||||
private JObject _data = null!;
|
||||
private JObject _data = null!;
|
||||
|
||||
public string CurrentCollection = ModCollection.DefaultCollectionName;
|
||||
public string DefaultCollection = ModCollection.DefaultCollectionName;
|
||||
public string ForcedCollection = string.Empty;
|
||||
public string CurrentCollection = ModCollection.DefaultCollectionName;
|
||||
public string DefaultCollection = ModCollection.DefaultCollectionName;
|
||||
public string ForcedCollection = string.Empty;
|
||||
public Dictionary<string, string> CharacterCollections = new();
|
||||
public Dictionary<string, string> ModSortOrder = new();
|
||||
public bool InvertModListOrder;
|
||||
public bool SortFoldersFirst;
|
||||
public SortModeV3 SortMode = SortModeV3.FoldersFirst;
|
||||
public Dictionary<string, string> ModSortOrder = new();
|
||||
public bool InvertModListOrder;
|
||||
public bool SortFoldersFirst;
|
||||
public SortModeV3 SortMode = SortModeV3.FoldersFirst;
|
||||
|
||||
public ConfigMigrationService(FilenameService fileNames, DalamudPluginInterface pi)
|
||||
{
|
||||
_fileNames = fileNames;
|
||||
_pluginInterface = pi;
|
||||
}
|
||||
public ConfigMigrationService(SaveService saveService)
|
||||
=> _saveService = saveService;
|
||||
|
||||
/// <summary> Add missing colors to the dictionary if necessary. </summary>
|
||||
private static void AddColors(Configuration config, bool forceSave)
|
||||
{
|
||||
var save = false;
|
||||
foreach (var color in Enum.GetValues<ColorId>())
|
||||
{
|
||||
save |= config.Colors.TryAdd(color, color.Data().DefaultColor);
|
||||
}
|
||||
|
||||
if (save || forceSave)
|
||||
{
|
||||
config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public void Migrate(Configuration config)
|
||||
|
|
@ -63,13 +57,13 @@ public class ConfigMigrationService
|
|||
// because it stayed alive for a bunch of people for some reason.
|
||||
DeleteMetaTmp();
|
||||
|
||||
if (config.Version >= Configuration.Constants.CurrentVersion || !File.Exists(_fileNames.ConfigFile))
|
||||
if (config.Version >= Configuration.Constants.CurrentVersion || !File.Exists(_saveService.FileNames.ConfigFile))
|
||||
{
|
||||
AddColors(config, false);
|
||||
return;
|
||||
}
|
||||
|
||||
_data = JObject.Parse(File.ReadAllText(_fileNames.ConfigFile));
|
||||
_data = JObject.Parse(File.ReadAllText(_saveService.FileNames.ConfigFile));
|
||||
CreateBackup();
|
||||
|
||||
Version0To1();
|
||||
|
|
@ -88,7 +82,7 @@ public class ConfigMigrationService
|
|||
if (_config.Version != 6)
|
||||
return;
|
||||
|
||||
ActiveCollectionMigration.MigrateUngenderedCollections(_fileNames);
|
||||
ActiveCollectionMigration.MigrateUngenderedCollections(_saveService.FileNames);
|
||||
_config.Version = 7;
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +109,7 @@ public class ConfigMigrationService
|
|||
return;
|
||||
|
||||
ModBackup.MigrateModBackups = true;
|
||||
_config.Version = 5;
|
||||
_config.Version = 5;
|
||||
}
|
||||
|
||||
// SortMode was changed from an enum to a type.
|
||||
|
|
@ -127,15 +121,15 @@ public class ConfigMigrationService
|
|||
SortMode = _data[nameof(SortMode)]?.ToObject<SortModeV3>() ?? SortMode;
|
||||
_config.SortMode = SortMode switch
|
||||
{
|
||||
SortModeV3.FoldersFirst => ISortMode<Mod>.FoldersFirst,
|
||||
SortModeV3.Lexicographical => ISortMode<Mod>.Lexicographical,
|
||||
SortModeV3.InverseFoldersFirst => ISortMode<Mod>.InverseFoldersFirst,
|
||||
SortModeV3.FoldersFirst => ISortMode<Mod>.FoldersFirst,
|
||||
SortModeV3.Lexicographical => ISortMode<Mod>.Lexicographical,
|
||||
SortModeV3.InverseFoldersFirst => ISortMode<Mod>.InverseFoldersFirst,
|
||||
SortModeV3.InverseLexicographical => ISortMode<Mod>.InverseLexicographical,
|
||||
SortModeV3.FoldersLast => ISortMode<Mod>.FoldersLast,
|
||||
SortModeV3.InverseFoldersLast => ISortMode<Mod>.InverseFoldersLast,
|
||||
SortModeV3.InternalOrder => ISortMode<Mod>.InternalOrder,
|
||||
SortModeV3.InternalOrderInverse => ISortMode<Mod>.InverseInternalOrder,
|
||||
_ => ISortMode<Mod>.FoldersFirst,
|
||||
SortModeV3.FoldersLast => ISortMode<Mod>.FoldersLast,
|
||||
SortModeV3.InverseFoldersLast => ISortMode<Mod>.InverseFoldersLast,
|
||||
SortModeV3.InternalOrder => ISortMode<Mod>.InternalOrder,
|
||||
SortModeV3.InternalOrderInverse => ISortMode<Mod>.InverseInternalOrder,
|
||||
_ => ISortMode<Mod>.FoldersFirst,
|
||||
};
|
||||
_config.Version = 4;
|
||||
}
|
||||
|
|
@ -147,8 +141,8 @@ public class ConfigMigrationService
|
|||
return;
|
||||
|
||||
SortFoldersFirst = _data[nameof(SortFoldersFirst)]?.ToObject<bool>() ?? false;
|
||||
SortMode = SortFoldersFirst ? SortModeV3.FoldersFirst : SortModeV3.Lexicographical;
|
||||
_config.Version = 3;
|
||||
SortMode = SortFoldersFirst ? SortModeV3.FoldersFirst : SortModeV3.Lexicographical;
|
||||
_config.Version = 3;
|
||||
}
|
||||
|
||||
// The forced collection was removed due to general inheritance.
|
||||
|
|
@ -192,7 +186,7 @@ public class ConfigMigrationService
|
|||
return;
|
||||
|
||||
// Add the previous forced collection to all current collections except itself as an inheritance.
|
||||
foreach (var collection in _fileNames.CollectionFiles)
|
||||
foreach (var collection in _saveService.FileNames.CollectionFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -215,10 +209,10 @@ public class ConfigMigrationService
|
|||
private void ResettleSortOrder()
|
||||
{
|
||||
ModSortOrder = _data[nameof(ModSortOrder)]?.ToObject<Dictionary<string, string>>() ?? ModSortOrder;
|
||||
var file = _fileNames.FilesystemFile;
|
||||
var file = _saveService.FileNames.FilesystemFile;
|
||||
using var stream = File.Open(file, File.Exists(file) ? FileMode.Truncate : FileMode.CreateNew);
|
||||
using var writer = new StreamWriter(stream);
|
||||
using var j = new JsonTextWriter(writer);
|
||||
using var j = new JsonTextWriter(writer);
|
||||
j.Formatting = Formatting.Indented;
|
||||
j.WriteStartObject();
|
||||
j.WritePropertyName("Data");
|
||||
|
|
@ -239,10 +233,10 @@ public class ConfigMigrationService
|
|||
// Move the active collections to their own file.
|
||||
private void ResettleCollectionSettings()
|
||||
{
|
||||
CurrentCollection = _data[nameof(CurrentCollection)]?.ToObject<string>() ?? CurrentCollection;
|
||||
DefaultCollection = _data[nameof(DefaultCollection)]?.ToObject<string>() ?? DefaultCollection;
|
||||
CurrentCollection = _data[nameof(CurrentCollection)]?.ToObject<string>() ?? CurrentCollection;
|
||||
DefaultCollection = _data[nameof(DefaultCollection)]?.ToObject<string>() ?? DefaultCollection;
|
||||
CharacterCollections = _data[nameof(CharacterCollections)]?.ToObject<Dictionary<string, string>>() ?? CharacterCollections;
|
||||
SaveActiveCollectionsV0(DefaultCollection, CurrentCollection, DefaultCollection,
|
||||
SaveActiveCollectionsV0(DefaultCollection, CurrentCollection, DefaultCollection,
|
||||
CharacterCollections.Select(kvp => (kvp.Key, kvp.Value)), Array.Empty<(CollectionType, string)>());
|
||||
}
|
||||
|
||||
|
|
@ -250,12 +244,12 @@ public class ConfigMigrationService
|
|||
private void SaveActiveCollectionsV0(string def, string ui, string current, IEnumerable<(string, string)> characters,
|
||||
IEnumerable<(CollectionType, string)> special)
|
||||
{
|
||||
var file = _fileNames.ActiveCollectionsFile;
|
||||
var file = _saveService.FileNames.ActiveCollectionsFile;
|
||||
try
|
||||
{
|
||||
using var stream = File.Open(file, File.Exists(file) ? FileMode.Truncate : FileMode.CreateNew);
|
||||
using var writer = new StreamWriter(stream);
|
||||
using var j = new JsonTextWriter(writer);
|
||||
using var j = new JsonTextWriter(writer);
|
||||
j.Formatting = Formatting.Indented;
|
||||
j.WriteStartObject();
|
||||
j.WritePropertyName(nameof(ActiveCollections.Default));
|
||||
|
|
@ -295,19 +289,18 @@ public class ConfigMigrationService
|
|||
return;
|
||||
|
||||
_config.ModDirectory = _data[nameof(CurrentCollection)]?.ToObject<string>() ?? string.Empty;
|
||||
_config.Version = 1;
|
||||
_config.Version = 1;
|
||||
ResettleCollectionJson();
|
||||
}
|
||||
|
||||
// Move the previous mod configurations to a new default collection file.
|
||||
/// <summary> Move the previous mod configurations to a new default collection file. </summary>
|
||||
private void ResettleCollectionJson()
|
||||
{
|
||||
var collectionJson = new FileInfo(Path.Combine(_config.ModDirectory, "collection.json"));
|
||||
if (!collectionJson.Exists)
|
||||
return;
|
||||
|
||||
var defaultCollection = ModCollection.CreateNewEmpty(ModCollection.DefaultCollectionName);
|
||||
var defaultCollectionFile = new FileInfo(_fileNames.CollectionFile(defaultCollection));
|
||||
var defaultCollectionFile = new FileInfo(_saveService.FileNames.CollectionFile(ModCollection.DefaultCollectionName));
|
||||
if (defaultCollectionFile.Exists)
|
||||
return;
|
||||
|
||||
|
|
@ -317,18 +310,18 @@ public class ConfigMigrationService
|
|||
var data = JArray.Parse(text);
|
||||
|
||||
var maxPriority = 0;
|
||||
var dict = new Dictionary<string, ModSettings.SavedSettings>();
|
||||
var dict = new Dictionary<string, ModSettings.SavedSettings>();
|
||||
foreach (var setting in data.Cast<JObject>())
|
||||
{
|
||||
var modName = (string)setting["FolderName"]!;
|
||||
var enabled = (bool)setting["Enabled"]!;
|
||||
var modName = (string)setting["FolderName"]!;
|
||||
var enabled = (bool)setting["Enabled"]!;
|
||||
var priority = (int)setting["Priority"]!;
|
||||
var settings = setting["Settings"]!.ToObject<Dictionary<string, long>>()
|
||||
?? setting["Conf"]!.ToObject<Dictionary<string, long>>();
|
||||
|
||||
dict[modName] = new ModSettings.SavedSettings()
|
||||
{
|
||||
Enabled = enabled,
|
||||
Enabled = enabled,
|
||||
Priority = priority,
|
||||
Settings = settings!,
|
||||
};
|
||||
|
|
@ -339,8 +332,9 @@ public class ConfigMigrationService
|
|||
if (!InvertModListOrder)
|
||||
dict = dict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority });
|
||||
|
||||
defaultCollection = ModCollection.MigrateFromV0(ModCollection.DefaultCollectionName, dict);
|
||||
Penumbra.SaveService.ImmediateSave(defaultCollection);
|
||||
var emptyStorage = new ModStorage();
|
||||
var collection = ModCollection.CreateFromData(_saveService, emptyStorage, ModCollection.DefaultCollectionName, 0, 1, dict);
|
||||
_saveService.ImmediateSave(new ModCollectionSave(emptyStorage, collection));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -352,7 +346,7 @@ public class ConfigMigrationService
|
|||
// Create a backup of the configuration file specifically.
|
||||
private void CreateBackup()
|
||||
{
|
||||
var name = _fileNames.ConfigFile;
|
||||
var name = _saveService.FileNames.ConfigFile;
|
||||
var bakName = name + ".bak";
|
||||
try
|
||||
{
|
||||
|
|
@ -366,13 +360,13 @@ public class ConfigMigrationService
|
|||
|
||||
public enum SortModeV3 : byte
|
||||
{
|
||||
FoldersFirst = 0x00,
|
||||
Lexicographical = 0x01,
|
||||
InverseFoldersFirst = 0x02,
|
||||
FoldersFirst = 0x00,
|
||||
Lexicographical = 0x01,
|
||||
InverseFoldersFirst = 0x02,
|
||||
InverseLexicographical = 0x03,
|
||||
FoldersLast = 0x04,
|
||||
InverseFoldersLast = 0x05,
|
||||
InternalOrder = 0x06,
|
||||
InternalOrderInverse = 0x07,
|
||||
FoldersLast = 0x04,
|
||||
InverseFoldersLast = 0x05,
|
||||
InternalOrder = 0x06,
|
||||
InternalOrderInverse = 0x07,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,15 +87,15 @@ public class CollectionsTab : IDisposable, ITab
|
|||
/// <summary> Draw the Clean Unused Settings button if there are any. </summary>
|
||||
private void DrawCleanCollectionButton(Vector2 width)
|
||||
{
|
||||
if (!_collectionManager.Active.Current.HasUnusedSettings)
|
||||
if (_collectionManager.Active.Current.UnusedSettings.Count == 0)
|
||||
return;
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.DrawDisabledButton(
|
||||
$"Clean {_collectionManager.Active.Current.NumUnusedSettings} Unused Settings###CleanSettings", width
|
||||
$"Clean {_collectionManager.Active.Current.UnusedSettings.Count} Unused Settings###CleanSettings", width
|
||||
, "Remove all stored settings for mods not currently available and fix invalid settings.\n\nUse at own risk."
|
||||
, false))
|
||||
_collectionManager.Active.Current.CleanUnavailableSettings();
|
||||
_collectionManager.Storage.CleanUnavailableSettings(_collectionManager.Active.Current);
|
||||
}
|
||||
|
||||
/// <summary> Draw the new collection input as well as its buttons. </summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue