This commit is contained in:
Ottermandias 2022-03-31 17:11:48 +02:00
parent a806dd28c3
commit d906e5aedf
13 changed files with 279 additions and 102 deletions

View file

@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.Mods;
using Penumbra.Util;
@ -65,23 +69,19 @@ public partial class ModCollection
switch( type )
{
case Type.Default:
Default = newCollection;
Penumbra.Config.DefaultCollection = newCollection.Name;
Default = newCollection;
Penumbra.ResidentResources.Reload();
Default.SetFiles();
break;
case Type.Current:
Current = newCollection;
Penumbra.Config.CurrentCollection = newCollection.Name;
Current = newCollection;
break;
case Type.Character:
_characters[ characterName! ] = newCollection;
Penumbra.Config.CharacterCollections[ characterName! ] = newCollection.Name;
_characters[ characterName! ] = newCollection;
break;
}
CollectionChanged?.Invoke( this[ oldCollectionIdx ], newCollection, type, characterName );
Penumbra.Config.Save();
CollectionChanged?.Invoke( type, this[ oldCollectionIdx ], newCollection, characterName );
}
public void SetCollection( ModCollection collection, Type type, string? characterName = null )
@ -95,10 +95,8 @@ public partial class ModCollection
return false;
}
_characters[ characterName ] = Empty;
Penumbra.Config.CharacterCollections[ characterName ] = Empty.Name;
Penumbra.Config.Save();
CollectionChanged?.Invoke( null, Empty, Type.Character, characterName );
_characters[ characterName ] = Empty;
CollectionChanged?.Invoke( Type.Character, null, Empty, characterName );
return true;
}
@ -109,12 +107,7 @@ public partial class ModCollection
{
RemoveCache( collection.Index );
_characters.Remove( characterName );
CollectionChanged?.Invoke( collection, null, Type.Character, characterName );
}
if( Penumbra.Config.CharacterCollections.Remove( characterName ) )
{
Penumbra.Config.Save();
CollectionChanged?.Invoke( Type.Character, collection, null, characterName );
}
}
@ -122,59 +115,66 @@ public partial class ModCollection
private int GetIndexForCollectionName( string name )
=> name.Length == 0 ? Empty.Index : _collections.IndexOf( c => c.Name == name );
public static string ActiveCollectionFile
=> Path.Combine( Dalamud.PluginInterface.ConfigDirectory.FullName, "active_collections.json" );
// Load default, current and character collections from config.
// Then create caches. If a collection does not exist anymore, reset it to an appropriate default.
public void LoadCollections()
{
var configChanged = false;
var defaultIdx = GetIndexForCollectionName( Penumbra.Config.DefaultCollection );
var file = ActiveCollectionFile;
var configChanged = true;
var jObject = new JObject();
if( File.Exists( file ) )
{
try
{
jObject = JObject.Parse( File.ReadAllText( file ) );
configChanged = false;
}
catch( Exception e )
{
PluginLog.Error( $"Could not read active collections from file {file}:\n{e}" );
}
}
var defaultName = jObject[ nameof( Default ) ]?.ToObject< string >() ?? Empty.Name;
var defaultIdx = GetIndexForCollectionName( defaultName );
if( defaultIdx < 0 )
{
PluginLog.Error( $"Last choice of Default Collection {Penumbra.Config.DefaultCollection} is not available, reset to None." );
Default = Empty;
Penumbra.Config.DefaultCollection = Default.Name;
configChanged = true;
PluginLog.Error( $"Last choice of Default Collection {defaultName} is not available, reset to {Empty.Name}." );
Default = Empty;
configChanged = true;
}
else
{
Default = this[ defaultIdx ];
}
var currentIdx = GetIndexForCollectionName( Penumbra.Config.CurrentCollection );
var currentName = jObject[ nameof( Current ) ]?.ToObject< string >() ?? DefaultCollection;
var currentIdx = GetIndexForCollectionName( currentName );
if( currentIdx < 0 )
{
PluginLog.Error( $"Last choice of Current Collection {Penumbra.Config.CurrentCollection} is not available, reset to Default." );
Current = DefaultName;
Penumbra.Config.DefaultCollection = Current.Name;
configChanged = true;
PluginLog.Error( $"Last choice of Current Collection {currentName} is not available, reset to {DefaultCollection}." );
Current = DefaultName;
configChanged = true;
}
else
{
Current = this[ currentIdx ];
}
if( LoadCharacterCollections() || configChanged )
{
Penumbra.Config.Save();
}
CreateNecessaryCaches();
}
// Load character collections. If a player name comes up multiple times, the last one is applied.
private bool LoadCharacterCollections()
{
var configChanged = false;
foreach( var (player, collectionName) in Penumbra.Config.CharacterCollections.ToArray() )
// Load character collections. If a player name comes up multiple times, the last one is applied.
var characters = jObject[ nameof( Characters ) ]?.ToObject< Dictionary< string, string > >() ?? new Dictionary< string, string >();
foreach( var (player, collectionName) in characters )
{
var idx = GetIndexForCollectionName( collectionName );
if( idx < 0 )
{
PluginLog.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to None." );
PluginLog.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {Empty.Name}." );
_characters.Add( player, Empty );
Penumbra.Config.CharacterCollections[ player ] = Empty.Name;
configChanged = true;
configChanged = true;
}
else
{
@ -182,7 +182,55 @@ public partial class ModCollection
}
}
return configChanged;
if( configChanged )
{
SaveActiveCollections();
}
CreateNecessaryCaches();
}
public void SaveActiveCollections()
=> SaveActiveCollections( Default.Name, Current.Name, Characters.Select( kvp => ( kvp.Key, kvp.Value.Name ) ) );
internal static void SaveActiveCollections( string def, string current, IEnumerable< (string, string) > characters )
{
var file = ActiveCollectionFile;
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 );
j.Formatting = Formatting.Indented;
j.WriteStartObject();
j.WritePropertyName( nameof( Default ) );
j.WriteValue( def );
j.WritePropertyName( nameof( Current ) );
j.WriteValue( current );
j.WritePropertyName( nameof( Characters ) );
j.WriteStartObject();
foreach( var (character, collection) in characters )
{
j.WritePropertyName( character, true );
j.WriteValue( collection );
}
j.WriteEndObject();
j.WriteEndObject();
}
catch( Exception e )
{
PluginLog.Error( $"Could not save active collections to file {file}:\n{e}" );
}
}
private void SaveOnChange( Type type, ModCollection? _1, ModCollection? _2, string? _3 )
{
if( type != Type.Inactive )
{
SaveActiveCollections();
}
}

View file

@ -24,10 +24,10 @@ public partial class ModCollection
{
// On addition, oldCollection is null. On deletion, newCollection is null.
// CharacterName is onls set for type == Character.
public delegate void CollectionChangeDelegate( ModCollection? oldCollection, ModCollection? newCollection, Type type,
public delegate void CollectionChangeDelegate( Type type, ModCollection? oldCollection, ModCollection? newCollection,
string? characterName = null );
private readonly Mods.Mod.Manager _modManager;
private readonly Mod.Manager _modManager;
// The empty collection is always available and always has index 0.
// It can not be deleted or moved.
@ -64,6 +64,7 @@ public partial class ModCollection
_modManager.ModDiscoveryStarted += OnModDiscoveryStarted;
_modManager.ModDiscoveryFinished += OnModDiscoveryFinished;
_modManager.ModChange += OnModChanged;
CollectionChanged += SaveOnChange;
ReadCollections();
LoadCollections();
}
@ -95,7 +96,7 @@ public partial class ModCollection
newCollection.Index = _collections.Count;
_collections.Add( newCollection );
newCollection.Save();
CollectionChanged?.Invoke( null, newCollection, Type.Inactive );
CollectionChanged?.Invoke( Type.Inactive, null, newCollection );
SetCollection( newCollection.Index, Type.Current );
return true;
}
@ -139,7 +140,7 @@ public partial class ModCollection
--_collections[ i ].Index;
}
CollectionChanged?.Invoke( collection, null, Type.Inactive );
CollectionChanged?.Invoke( Type.Inactive, collection, null );
return true;
}

View file

@ -5,11 +5,10 @@ using Dalamud.Logging;
namespace Penumbra;
[Serializable]
public partial class Configuration : IPluginConfiguration
{
private const int CurrentVersion = 1;
private const int CurrentVersion = 2;
public int Version { get; set; } = CurrentVersion;
@ -35,26 +34,19 @@ public partial class Configuration : IPluginConfiguration
public string ModDirectory { get; set; } = string.Empty;
public string CurrentCollection { get; set; } = "Default";
public string DefaultCollection { get; set; } = "Default";
public bool SortFoldersFirst { get; set; } = false;
public bool HasReadCharacterCollectionDesc { get; set; } = false;
public Dictionary< string, string > CharacterCollections { get; set; } = new();
public Dictionary< string, string > ModSortOrder { get; set; } = new();
public static Configuration Load()
{
var configuration = Dalamud.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
if( configuration.Version == CurrentVersion )
var iConfiguration = Dalamud.PluginInterface.GetPluginConfig();
var configuration = iConfiguration as Configuration ?? new Configuration();
if( iConfiguration is { Version: CurrentVersion } )
{
return configuration;
}
MigrateConfiguration.Version0To1( configuration );
MigrateConfiguration.Migrate( configuration );
configuration.Save();
return configuration;

View file

@ -179,7 +179,7 @@ public unsafe partial class PathResolver
}
// Update collections linked to Game/DrawObjects due to a change in collection configuration.
private void CheckCollections( ModCollection? _1, ModCollection? _2, ModCollection.Type type, string? name )
private void CheckCollections( ModCollection.Type type, ModCollection? _1, ModCollection? _2, string? name )
{
if( type is not (ModCollection.Type.Character or ModCollection.Type.Default) )
{

View file

@ -103,7 +103,7 @@ public partial class PathResolver : IDisposable
Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange;
}
private void OnCollectionChange( ModCollection? _1, ModCollection? _2, ModCollection.Type type, string? characterName )
private void OnCollectionChange( ModCollection.Type type, ModCollection? _1, ModCollection? _2, string? characterName )
{
if( type != ModCollection.Type.Character )
{

View file

@ -3,37 +3,128 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Filesystem;
using Penumbra.Collections;
using Penumbra.Mods;
namespace Penumbra;
public partial class Configuration
public class MigrateConfiguration
{
public string ForcedCollection { internal get; set; } = "";
public bool InvertModListOrder { internal get; set; }
}
private Configuration _config = null!;
private JObject _data = null!;
public static class MigrateConfiguration
{
public static void Version0To1( Configuration config )
public string CurrentCollection = ModCollection.DefaultCollection;
public string DefaultCollection = ModCollection.DefaultCollection;
public string ForcedCollection = string.Empty;
public Dictionary< string, string > CharacterCollections = new();
public Dictionary< string, string > ModSortOrder = new();
public bool InvertModListOrder = false;
public static void Migrate( Configuration config )
{
if( config.Version != 0 )
var m = new MigrateConfiguration
{
_config = config,
_data = JObject.Parse( File.ReadAllText( Dalamud.PluginInterface.ConfigFile.FullName ) ),
};
m.CreateBackup();
m.Version0To1();
m.Version1To2();
}
private void Version1To2()
{
if( _config.Version != 1 )
{
return;
}
config.ModDirectory = config.CurrentCollection;
config.CurrentCollection = "Default";
config.DefaultCollection = "Default";
config.Version = 1;
ResettleCollectionJson( config );
ResettleSortOrder();
ResettleCollectionSettings();
ResettleForcedCollection();
_config.Version = 2;
}
private static void ResettleCollectionJson( Configuration config )
private void ResettleForcedCollection()
{
var collectionJson = new FileInfo( Path.Combine( config.ModDirectory, "collection.json" ) );
ForcedCollection = _data[ nameof( ForcedCollection ) ]?.ToObject< string >() ?? ForcedCollection;
if( ForcedCollection.Length <= 0 )
{
return;
}
foreach( var collection in Directory.EnumerateFiles( ModCollection.CollectionDirectory, "*.json" ) )
{
try
{
var jObject = JObject.Parse( File.ReadAllText( collection ) );
if( jObject[ nameof( ModCollection.Name ) ]?.ToObject< string >() != ForcedCollection )
{
jObject[ nameof( ModCollection.Inheritance ) ] = JToken.FromObject( new List< string >() { ForcedCollection } );
File.WriteAllText( collection, jObject.ToString() );
}
}
catch( Exception e )
{
PluginLog.Error(
$"Could not transfer forced collection {ForcedCollection} to inheritance of collection {collection}:\n{e}" );
}
}
}
private void ResettleSortOrder()
{
ModSortOrder = _data[ nameof( ModSortOrder ) ]?.ToObject< Dictionary< string, string > >() ?? ModSortOrder;
var file = Mod2.Manager.ModFileSystemFile;
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 );
j.Formatting = Formatting.Indented;
j.WriteStartObject();
j.WritePropertyName( "Data" );
j.WriteStartObject();
foreach( var (mod, path) in ModSortOrder )
{
j.WritePropertyName( mod, true );
j.WriteValue( path );
}
j.WriteEndObject();
j.WritePropertyName( "EmptyFolders" );
j.WriteStartArray();
j.WriteEndArray();
j.WriteEndObject();
}
private void ResettleCollectionSettings()
{
CurrentCollection = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? CurrentCollection;
DefaultCollection = _data[ nameof( DefaultCollection ) ]?.ToObject< string >() ?? DefaultCollection;
CharacterCollections = _data[ nameof( CharacterCollections ) ]?.ToObject< Dictionary< string, string > >() ?? CharacterCollections;
ModCollection.Manager.SaveActiveCollections( DefaultCollection, CurrentCollection,
CharacterCollections.Select( kvp => ( kvp.Key, kvp.Value ) ) );
}
private void Version0To1()
{
if( _config.Version != 0 )
{
return;
}
_config.ModDirectory = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? string.Empty;
_config.Version = 1;
ResettleCollectionJson();
}
private void ResettleCollectionJson()
{
var collectionJson = new FileInfo( Path.Combine( _config.ModDirectory, "collection.json" ) );
if( !collectionJson.Exists )
{
return;
@ -71,7 +162,8 @@ public static class MigrateConfiguration
maxPriority = Math.Max( maxPriority, priority );
}
if( !config.InvertModListOrder )
InvertModListOrder = _data[ nameof( InvertModListOrder ) ]?.ToObject< bool >() ?? InvertModListOrder;
if( !InvertModListOrder )
{
foreach( var setting in dict.Values )
{
@ -88,4 +180,18 @@ public static class MigrateConfiguration
throw;
}
}
private void CreateBackup()
{
var name = Dalamud.PluginInterface.ConfigFile.FullName;
var bakName = name + ".bak";
try
{
File.Copy( name, bakName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not create backup copy of config at {bakName}:\n{e}" );
}
}
}

View file

@ -0,0 +1,12 @@
using System.IO;
namespace Penumbra.Mods;
public sealed partial class Mod2
{
public sealed partial class Manager
{
public static string ModFileSystemFile
=> Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(), "sort_order.json" );
}
}

View file

@ -1,6 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json.Linq;
namespace Penumbra.Mods;

View file

@ -128,7 +128,7 @@ public static partial class ModFileSystem
{
foreach( var mod in target.AllMods( true ) )
{
Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName;
Penumbra.ModManager.TemporaryModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName;
}
Penumbra.Config.Save();
@ -136,16 +136,16 @@ public static partial class ModFileSystem
}
// Sets and saves the sort order of a single mod, removing the entry if it is unnecessary.
private static void SaveMod( global::Penumbra.Mods.Mod mod )
private static void SaveMod( Mod mod )
{
if( ReferenceEquals( mod.Order.ParentFolder, Root )
&& string.Equals( mod.Order.SortOrderName, mod.Meta.Name.Text.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) )
{
Penumbra.Config.ModSortOrder.Remove( mod.BasePath.Name );
Penumbra.ModManager.TemporaryModSortOrder.Remove( mod.BasePath.Name );
}
else
{
Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName;
Penumbra.ModManager.TemporaryModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName;
}
Penumbra.Config.Save();
@ -183,7 +183,7 @@ public static partial class ModFileSystem
return true;
}
private static bool RenameNoSave( global::Penumbra.Mods.Mod mod, string newName )
private static bool RenameNoSave( Mod mod, string newName )
{
newName = newName.Replace( '/', '\\' );
if( mod.Order.SortOrderName == newName )
@ -192,12 +192,12 @@ public static partial class ModFileSystem
}
mod.Order.ParentFolder.RemoveModIgnoreEmpty( mod );
mod.Order = new global::Penumbra.Mods.Mod.SortOrder( mod.Order.ParentFolder, newName );
mod.Order = new Mod.SortOrder( mod.Order.ParentFolder, newName );
mod.Order.ParentFolder.AddMod( mod );
return true;
}
private static bool MoveNoSave( global::Penumbra.Mods.Mod mod, ModFolder target )
private static bool MoveNoSave( Mod mod, ModFolder target )
{
var oldParent = mod.Order.ParentFolder;
if( ReferenceEquals( target, oldParent ) )
@ -206,7 +206,7 @@ public static partial class ModFileSystem
}
oldParent.RemoveMod( mod );
mod.Order = new global::Penumbra.Mods.Mod.SortOrder( target, mod.Order.SortOrderName );
mod.Order = new Mod.SortOrder( target, mod.Order.SortOrderName );
target.AddMod( mod );
return true;
}

View file

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.ByteString;
using Penumbra.Meta;
using Penumbra.Mods;
@ -103,21 +104,35 @@ public partial class Mod
public Manager()
{
SetBaseDirectory( Config.ModDirectory, true );
// TODO
try
{
var data = JObject.Parse( File.ReadAllText( Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(),
"sort_order.json" ) ) );
TemporaryModSortOrder = data["Data"]?.ToObject<Dictionary<string, string>>() ?? new Dictionary<string, string>();
}
catch
{
TemporaryModSortOrder = new Dictionary<string, string>();
}
}
public Dictionary<string, string> TemporaryModSortOrder;
private bool SetSortOrderPath( Mod mod, string path )
{
mod.Move( path );
var fixedPath = mod.Order.FullPath;
if( fixedPath.Length == 0 || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) )
{
Config.ModSortOrder.Remove( mod.BasePath.Name );
Penumbra.ModManager.TemporaryModSortOrder.Remove( mod.BasePath.Name );
return true;
}
if( path != fixedPath )
{
Config.ModSortOrder[ mod.BasePath.Name ] = fixedPath;
TemporaryModSortOrder[ mod.BasePath.Name ] = fixedPath;
return true;
}
@ -128,7 +143,7 @@ public partial class Mod
{
var changes = false;
foreach( var (folder, path) in Config.ModSortOrder.ToArray() )
foreach( var (folder, path) in TemporaryModSortOrder.ToArray() )
{
if( path.Length > 0 && _mods.FindFirst( m => m.BasePath.Name == folder, out var mod ) )
{
@ -137,7 +152,7 @@ public partial class Mod
else if( removeOldPaths )
{
changes = true;
Config.ModSortOrder.Remove( folder );
TemporaryModSortOrder.Remove( folder );
}
}
@ -212,7 +227,7 @@ public partial class Mod
return -1;
}
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
if( TemporaryModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{
if( SetSortOrderPath( mod, sortOrder ) )
{
@ -246,13 +261,13 @@ public partial class Mod
if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) )
{
mod.ComputeChangedItems();
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
if( TemporaryModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{
mod.Move( sortOrder );
var path = mod.Order.FullPath;
if( path != sortOrder )
{
Config.ModSortOrder[ mod.BasePath.Name ] = path;
TemporaryModSortOrder[ mod.BasePath.Name ] = path;
Config.Save();
}
}

View file

@ -35,12 +35,12 @@ public static class ModManagerEditExtensions
if( newSortOrder == string.Empty || newSortOrder == inRoot.SortOrderName )
{
mod.Order = inRoot;
manager.Config.ModSortOrder.Remove( mod.BasePath.Name );
manager.TemporaryModSortOrder.Remove( mod.BasePath.Name );
}
else
{
mod.Move( newSortOrder );
manager.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullPath;
manager.TemporaryModSortOrder[ mod.BasePath.Name ] = mod.Order.FullPath;
}
manager.Config.Save();
@ -75,10 +75,10 @@ public static class ModManagerEditExtensions
mod.MetaFile = Mod.MetaFileInfo( newDir );
manager.UpdateMod( mod );
if( manager.Config.ModSortOrder.ContainsKey( oldBasePath.Name ) )
if( manager.TemporaryModSortOrder.ContainsKey( oldBasePath.Name ) )
{
manager.Config.ModSortOrder[ newDir.Name ] = manager.Config.ModSortOrder[ oldBasePath.Name ];
manager.Config.ModSortOrder.Remove( oldBasePath.Name );
manager.TemporaryModSortOrder[ newDir.Name ] = manager.TemporaryModSortOrder[ oldBasePath.Name ];
manager.TemporaryModSortOrder.Remove( oldBasePath.Name );
manager.Config.Save();
}

View file

@ -70,6 +70,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
<ProjectReference Include="..\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj" />
</ItemGroup>

View file

@ -81,7 +81,7 @@ public partial class SettingsInterface
if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 ) && modManager.RenameMod( name, Mod!.Data ) )
{
_selector.SelectModOnUpdate( Mod.Data.BasePath.Name );
if( !modManager.Config.ModSortOrder.ContainsKey( Mod!.Data.BasePath.Name ) )
if( !modManager.TemporaryModSortOrder.ContainsKey( Mod!.Data.BasePath.Name ) )
{
Mod.Data.Rename( name );
}