mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Start ModManager dissemination....
This commit is contained in:
parent
174e640c45
commit
c8415e3079
34 changed files with 1305 additions and 1542 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ public class PenumbraIpcProviders : IDisposable
|
|||
internal readonly FuncProvider< string, int, PenumbraApiEc > RemoveTemporaryModAll;
|
||||
internal readonly FuncProvider< string, string, int, PenumbraApiEc > RemoveTemporaryMod;
|
||||
|
||||
public PenumbraIpcProviders( DalamudPluginInterface pi, IPenumbraApi api )
|
||||
public PenumbraIpcProviders( DalamudPluginInterface pi, IPenumbraApi api, Mod.Manager modManager )
|
||||
{
|
||||
Api = api;
|
||||
|
||||
|
|
@ -219,7 +220,7 @@ public class PenumbraIpcProviders : IDisposable
|
|||
RemoveTemporaryModAll = Ipc.RemoveTemporaryModAll.Provider( pi, Api.RemoveTemporaryModAll );
|
||||
RemoveTemporaryMod = Ipc.RemoveTemporaryMod.Provider( pi, Api.RemoveTemporaryMod );
|
||||
|
||||
Tester = new IpcTester( pi, this );
|
||||
Tester = new IpcTester( pi, this, modManager );
|
||||
|
||||
Initialized.Invoke();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Penumbra.Collections;
|
|||
|
||||
public partial class ModCollection
|
||||
{
|
||||
public sealed partial class Manager : ISaveable
|
||||
public sealed partial class Manager : ISavable
|
||||
{
|
||||
public const int Version = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ using Penumbra.Util;
|
|||
namespace Penumbra.Collections;
|
||||
|
||||
// File operations like saving, loading and deleting for a collection.
|
||||
public partial class ModCollection : ISaveable
|
||||
public partial class ModCollection : ISavable
|
||||
{
|
||||
// Since inheritances depend on other collections existing,
|
||||
// we return them as a list to be applied after reading all collections.
|
||||
|
|
|
|||
|
|
@ -9,23 +9,21 @@ using OtterGui.Classes;
|
|||
using OtterGui.Filesystem;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.Util;
|
||||
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
public class Configuration : IPluginConfiguration, ISavable
|
||||
{
|
||||
[JsonIgnore]
|
||||
private readonly string _fileName;
|
||||
|
||||
[JsonIgnore]
|
||||
private readonly FrameworkManager _framework;
|
||||
private readonly SaveService _saveService;
|
||||
|
||||
public int Version { get; set; } = Constants.CurrentVersion;
|
||||
|
||||
|
|
@ -101,14 +99,13 @@ public class Configuration : IPluginConfiguration
|
|||
/// Load the current configuration.
|
||||
/// Includes adding new colors and migrating from old versions.
|
||||
/// </summary>
|
||||
public Configuration(FilenameService fileNames, ConfigMigrationService migrator, FrameworkManager framework)
|
||||
public Configuration(FilenameService fileNames, ConfigMigrationService migrator, SaveService saveService)
|
||||
{
|
||||
_fileName = fileNames.ConfigFile;
|
||||
_framework = framework;
|
||||
Load(migrator);
|
||||
_saveService = saveService;
|
||||
Load(fileNames, migrator);
|
||||
}
|
||||
|
||||
public void Load(ConfigMigrationService migrator)
|
||||
public void Load(FilenameService fileNames, ConfigMigrationService migrator)
|
||||
{
|
||||
static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs)
|
||||
{
|
||||
|
|
@ -117,9 +114,9 @@ public class Configuration : IPluginConfiguration
|
|||
errorArgs.ErrorContext.Handled = true;
|
||||
}
|
||||
|
||||
if (File.Exists(_fileName))
|
||||
if (File.Exists(fileNames.ConfigFile))
|
||||
{
|
||||
var text = File.ReadAllText(_fileName);
|
||||
var text = File.ReadAllText(fileNames.ConfigFile);
|
||||
JsonConvert.PopulateObject(text, this, new JsonSerializerSettings
|
||||
{
|
||||
Error = HandleDeserializationError,
|
||||
|
|
@ -130,21 +127,8 @@ public class Configuration : IPluginConfiguration
|
|||
}
|
||||
|
||||
/// <summary> Save the current configuration. </summary>
|
||||
private void SaveConfiguration()
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = JsonConvert.SerializeObject(this, Formatting.Indented);
|
||||
File.WriteAllText(_fileName, text);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not save plugin configuration:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
=> _framework.RegisterDelayed(nameof(SaveConfiguration), SaveConfiguration);
|
||||
=> _saveService.QueueSave(this);
|
||||
|
||||
/// <summary> Contains some default values or boundaries for config values. </summary>
|
||||
public static class Constants
|
||||
|
|
@ -192,4 +176,14 @@ public class Configuration : IPluginConfiguration
|
|||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.ConfigFile;
|
||||
|
||||
public void Save(StreamWriter writer)
|
||||
{
|
||||
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||
serializer.Serialize(jWriter, this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Mods;
|
||||
using FileMode = System.IO.FileMode;
|
||||
|
|
@ -33,22 +34,19 @@ public partial class TexToolsImporter : IDisposable
|
|||
public ImporterState State { get; private set; }
|
||||
public readonly List< (FileInfo File, DirectoryInfo? Mod, Exception? Error) > ExtractedMods;
|
||||
|
||||
public TexToolsImporter( DirectoryInfo baseDirectory, ICollection< FileInfo > files,
|
||||
Action< FileInfo, DirectoryInfo?, Exception? > handler, Configuration config, ModEditor editor)
|
||||
: this( baseDirectory, files.Count, files, handler, config, editor)
|
||||
{ }
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly ModEditor _editor;
|
||||
private readonly ModEditor _editor;
|
||||
private readonly Mod.Manager _modManager;
|
||||
|
||||
public TexToolsImporter( DirectoryInfo baseDirectory, int count, IEnumerable< FileInfo > modPackFiles,
|
||||
Action< FileInfo, DirectoryInfo?, Exception? > handler, Configuration config, ModEditor editor)
|
||||
Action< FileInfo, DirectoryInfo?, Exception? > handler, Configuration config, ModEditor editor, Mod.Manager modManager)
|
||||
{
|
||||
_baseDirectory = baseDirectory;
|
||||
_tmpFile = Path.Combine( _baseDirectory.FullName, TempFileName );
|
||||
_modPackFiles = modPackFiles;
|
||||
_config = config;
|
||||
_editor = editor;
|
||||
_editor = editor;
|
||||
_modManager = modManager;
|
||||
_modPackCount = count;
|
||||
ExtractedMods = new List< (FileInfo, DirectoryInfo?, Exception?) >( count );
|
||||
_token = _cancellation.Token;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public partial class TexToolsImporter
|
|||
|
||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) );
|
||||
// Create a new ModMeta from the TTMP mod list info
|
||||
Mod.Creator.CreateMeta( _currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description, null, null );
|
||||
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description, null, null );
|
||||
|
||||
// Open the mod data file from the mod pack as a SqPackStream
|
||||
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
|
||||
|
|
@ -90,7 +90,7 @@ public partial class TexToolsImporter
|
|||
Penumbra.Log.Information( " -> Importing Simple V2 ModPack" );
|
||||
|
||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, _currentModName );
|
||||
Mod.Creator.CreateMeta( _currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty( modList.Description )
|
||||
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty( modList.Description )
|
||||
? "Mod imported from TexTools mod pack"
|
||||
: modList.Description, modList.Version, modList.Url );
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ public partial class TexToolsImporter
|
|||
_currentModName = modList.Name;
|
||||
|
||||
_currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, _currentModName );
|
||||
Mod.Creator.CreateMeta( _currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version, modList.Url );
|
||||
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version, modList.Url );
|
||||
|
||||
if( _currentNumOptions == 0 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ public class DuplicateManager
|
|||
try
|
||||
{
|
||||
var mod = new Mod(modDirectory);
|
||||
mod.Reload(true, out _);
|
||||
mod.Reload(_modManager, true, out _);
|
||||
|
||||
Finished = false;
|
||||
_files.UpdateAll(mod, mod.Default);
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ public class ModFileEditor
|
|||
if (deletions <= 0)
|
||||
return;
|
||||
|
||||
mod.Reload(false, out _);
|
||||
mod.Reload(_modManager, false, out _);
|
||||
_files.UpdateAll(mod, option);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,20 +8,20 @@ public partial class Mod
|
|||
{
|
||||
public partial class Manager
|
||||
{
|
||||
public delegate void ModPathChangeDelegate( ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
|
||||
DirectoryInfo? newDirectory );
|
||||
public delegate void ModPathChangeDelegate(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
|
||||
DirectoryInfo? newDirectory);
|
||||
|
||||
public event ModPathChangeDelegate ModPathChanged;
|
||||
|
||||
// Rename/Move a mod directory.
|
||||
// Updates all collection settings and sort order settings.
|
||||
public void MoveModDirectory( int idx, string newName )
|
||||
public void MoveModDirectory(int idx, string newName)
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
var mod = this[idx];
|
||||
var oldName = mod.Name;
|
||||
var oldDirectory = mod.ModPath;
|
||||
|
||||
switch( NewDirectoryValid( oldDirectory.Name, newName, out var dir ) )
|
||||
switch (NewDirectoryValid(oldDirectory.Name, newName, out var dir))
|
||||
{
|
||||
case NewDirectoryState.NonExisting:
|
||||
// Nothing to do
|
||||
|
|
@ -29,11 +29,11 @@ public partial class Mod
|
|||
case NewDirectoryState.ExistsEmpty:
|
||||
try
|
||||
{
|
||||
Directory.Delete( dir!.FullName );
|
||||
Directory.Delete(dir!.FullName);
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not delete empty directory {dir!.FullName} to move {mod.Name} to it:\n{e}" );
|
||||
Penumbra.Log.Error($"Could not delete empty directory {dir!.FullName} to move {mod.Name} to it:\n{e}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -50,105 +50,97 @@ public partial class Mod
|
|||
|
||||
try
|
||||
{
|
||||
Directory.Move( oldDirectory.FullName, dir!.FullName );
|
||||
Directory.Move(oldDirectory.FullName, dir!.FullName);
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not move {mod.Name} from {oldDirectory.Name} to {dir!.Name}:\n{e}" );
|
||||
Penumbra.Log.Error($"Could not move {mod.Name} from {oldDirectory.Name} to {dir!.Name}:\n{e}");
|
||||
return;
|
||||
}
|
||||
|
||||
MoveDataFile( oldDirectory, dir );
|
||||
new ModBackup( this, mod ).Move( null, dir.Name );
|
||||
DataEditor.MoveDataFile(oldDirectory, dir);
|
||||
new ModBackup(this, mod).Move(null, dir.Name);
|
||||
|
||||
dir.Refresh();
|
||||
mod.ModPath = dir;
|
||||
if( !mod.Reload( false, out var metaChange ) )
|
||||
if (!mod.Reload(this, false, out var metaChange))
|
||||
{
|
||||
Penumbra.Log.Error( $"Error reloading moved mod {mod.Name}." );
|
||||
Penumbra.Log.Error($"Error reloading moved mod {mod.Name}.");
|
||||
return;
|
||||
}
|
||||
|
||||
ModPathChanged.Invoke( ModPathChangeType.Moved, mod, oldDirectory, dir );
|
||||
if( metaChange != ModDataChangeType.None )
|
||||
{
|
||||
ModDataChanged?.Invoke( metaChange, mod, oldName );
|
||||
}
|
||||
ModPathChanged.Invoke(ModPathChangeType.Moved, mod, oldDirectory, dir);
|
||||
if (metaChange != ModDataChangeType.None)
|
||||
_communicator.ModDataChanged.Invoke(metaChange, mod, oldName);
|
||||
}
|
||||
|
||||
// Reload a mod without changing its base directory.
|
||||
// If the base directory does not exist anymore, the mod will be deleted.
|
||||
public void ReloadMod( int idx )
|
||||
/// <summary>
|
||||
/// Reload a mod without changing its base directory.
|
||||
/// If the base directory does not exist anymore, the mod will be deleted.
|
||||
/// </summary>
|
||||
public void ReloadMod(int idx)
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
var mod = this[idx];
|
||||
var oldName = mod.Name;
|
||||
|
||||
ModPathChanged.Invoke( ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath );
|
||||
if( !mod.Reload( true, out var metaChange ) )
|
||||
ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath);
|
||||
if (!mod.Reload(this, true, out var metaChange))
|
||||
{
|
||||
Penumbra.Log.Warning( mod.Name.Length == 0
|
||||
Penumbra.Log.Warning(mod.Name.Length == 0
|
||||
? $"Reloading mod {oldName} has failed, new name is empty. Deleting instead."
|
||||
: $"Reloading mod {oldName} failed, {mod.ModPath.FullName} does not exist anymore or it ha. Deleting instead." );
|
||||
: $"Reloading mod {oldName} failed, {mod.ModPath.FullName} does not exist anymore or it ha. Deleting instead.");
|
||||
|
||||
DeleteMod( idx );
|
||||
DeleteMod(idx);
|
||||
return;
|
||||
}
|
||||
|
||||
ModPathChanged.Invoke( ModPathChangeType.Reloaded, mod, mod.ModPath, mod.ModPath );
|
||||
if( metaChange != ModDataChangeType.None )
|
||||
{
|
||||
ModDataChanged?.Invoke( metaChange, mod, oldName );
|
||||
}
|
||||
ModPathChanged.Invoke(ModPathChangeType.Reloaded, mod, mod.ModPath, mod.ModPath);
|
||||
if (metaChange != ModDataChangeType.None)
|
||||
_communicator.ModDataChanged.Invoke(metaChange, mod, oldName);
|
||||
}
|
||||
|
||||
// Delete a mod by its index. The event is invoked before the mod is removed from the list.
|
||||
// Deletes from filesystem as well as from internal data.
|
||||
// Updates indices of later mods.
|
||||
public void DeleteMod( int idx )
|
||||
/// <summary>
|
||||
/// Delete a mod by its index. The event is invoked before the mod is removed from the list.
|
||||
/// Deletes from filesystem as well as from internal data.
|
||||
/// Updates indices of later mods.
|
||||
/// </summary>
|
||||
public void DeleteMod(int idx)
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( Directory.Exists( mod.ModPath.FullName ) )
|
||||
{
|
||||
var mod = this[idx];
|
||||
if (Directory.Exists(mod.ModPath.FullName))
|
||||
try
|
||||
{
|
||||
Directory.Delete( mod.ModPath.FullName, true );
|
||||
Penumbra.Log.Debug( $"Deleted directory {mod.ModPath.FullName} for {mod.Name}." );
|
||||
Directory.Delete(mod.ModPath.FullName, true);
|
||||
Penumbra.Log.Debug($"Deleted directory {mod.ModPath.FullName} for {mod.Name}.");
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not delete the mod {mod.ModPath.Name}:\n{e}" );
|
||||
Penumbra.Log.Error($"Could not delete the mod {mod.ModPath.Name}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
ModPathChanged.Invoke( ModPathChangeType.Deleted, mod, mod.ModPath, null );
|
||||
_mods.RemoveAt( idx );
|
||||
foreach( var remainingMod in _mods.Skip( idx ) )
|
||||
{
|
||||
ModPathChanged.Invoke(ModPathChangeType.Deleted, mod, mod.ModPath, null);
|
||||
_mods.RemoveAt(idx);
|
||||
foreach (var remainingMod in _mods.Skip(idx))
|
||||
--remainingMod.Index;
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug( $"Deleted mod {mod.Name}." );
|
||||
Penumbra.Log.Debug($"Deleted mod {mod.Name}.");
|
||||
}
|
||||
|
||||
// Load a new mod and add it to the manager if successful.
|
||||
public void AddMod( DirectoryInfo modFolder )
|
||||
/// <summary> Load a new mod and add it to the manager if successful. </summary>
|
||||
public void AddMod(DirectoryInfo modFolder)
|
||||
{
|
||||
if( _mods.Any( m => m.ModPath.Name == modFolder.Name ) )
|
||||
{
|
||||
if (_mods.Any(m => m.ModPath.Name == modFolder.Name))
|
||||
return;
|
||||
}
|
||||
|
||||
Creator.SplitMultiGroups( modFolder );
|
||||
var mod = LoadMod( modFolder, true );
|
||||
if( mod == null )
|
||||
{
|
||||
Creator.SplitMultiGroups(modFolder);
|
||||
var mod = LoadMod(this, modFolder, true);
|
||||
if (mod == null)
|
||||
return;
|
||||
}
|
||||
|
||||
mod.Index = _mods.Count;
|
||||
_mods.Add( mod );
|
||||
ModPathChanged.Invoke( ModPathChangeType.Added, mod, null, mod.ModPath );
|
||||
Penumbra.Log.Debug( $"Added new mod {mod.Name} from {modFolder.FullName}." );
|
||||
_mods.Add(mod);
|
||||
ModPathChanged.Invoke(ModPathChangeType.Added, mod, null, mod.ModPath);
|
||||
Penumbra.Log.Debug($"Added new mod {mod.Name} from {modFolder.FullName}.");
|
||||
}
|
||||
|
||||
public enum NewDirectoryState
|
||||
|
|
@ -162,66 +154,52 @@ public partial class Mod
|
|||
Empty,
|
||||
}
|
||||
|
||||
// Return the state of the new potential name of a directory.
|
||||
public NewDirectoryState NewDirectoryValid( string oldName, string newName, out DirectoryInfo? directory )
|
||||
/// <summary> Return the state of the new potential name of a directory. </summary>
|
||||
public NewDirectoryState NewDirectoryValid(string oldName, string newName, out DirectoryInfo? directory)
|
||||
{
|
||||
directory = null;
|
||||
if( newName.Length == 0 )
|
||||
{
|
||||
if (newName.Length == 0)
|
||||
return NewDirectoryState.Empty;
|
||||
}
|
||||
|
||||
if( oldName == newName )
|
||||
{
|
||||
if (oldName == newName)
|
||||
return NewDirectoryState.Identical;
|
||||
}
|
||||
|
||||
var fixedNewName = Creator.ReplaceBadXivSymbols( newName );
|
||||
if( fixedNewName != newName )
|
||||
{
|
||||
var fixedNewName = Creator.ReplaceBadXivSymbols(newName);
|
||||
if (fixedNewName != newName)
|
||||
return NewDirectoryState.ContainsInvalidSymbols;
|
||||
}
|
||||
|
||||
directory = new DirectoryInfo( Path.Combine( BasePath.FullName, fixedNewName ) );
|
||||
if( File.Exists( directory.FullName ) )
|
||||
{
|
||||
directory = new DirectoryInfo(Path.Combine(BasePath.FullName, fixedNewName));
|
||||
if (File.Exists(directory.FullName))
|
||||
return NewDirectoryState.ExistsAsFile;
|
||||
}
|
||||
|
||||
if( !Directory.Exists( directory.FullName ) )
|
||||
{
|
||||
if (!Directory.Exists(directory.FullName))
|
||||
return NewDirectoryState.NonExisting;
|
||||
}
|
||||
|
||||
if( directory.EnumerateFileSystemInfos().Any() )
|
||||
{
|
||||
if (directory.EnumerateFileSystemInfos().Any())
|
||||
return NewDirectoryState.ExistsNonEmpty;
|
||||
}
|
||||
|
||||
return NewDirectoryState.ExistsEmpty;
|
||||
}
|
||||
|
||||
|
||||
// Add new mods to NewMods and remove deleted mods from NewMods.
|
||||
private void OnModPathChange( ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
|
||||
DirectoryInfo? newDirectory )
|
||||
/// <summary> Add new mods to NewMods and remove deleted mods from NewMods. </summary>
|
||||
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
|
||||
DirectoryInfo? newDirectory)
|
||||
{
|
||||
switch( type )
|
||||
switch (type)
|
||||
{
|
||||
case ModPathChangeType.Added:
|
||||
NewMods.Add( mod );
|
||||
NewMods.Add(mod);
|
||||
break;
|
||||
case ModPathChangeType.Deleted:
|
||||
NewMods.Remove( mod );
|
||||
NewMods.Remove(mod);
|
||||
break;
|
||||
case ModPathChangeType.Moved:
|
||||
if( oldDirectory != null && newDirectory != null )
|
||||
{
|
||||
MoveDataFile( oldDirectory, newDirectory );
|
||||
}
|
||||
if (oldDirectory != null && newDirectory != null)
|
||||
DataEditor.MoveDataFile(oldDirectory, newDirectory);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,67 +7,6 @@ public sealed partial class Mod
|
|||
{
|
||||
public partial class Manager
|
||||
{
|
||||
public void ChangeModFavorite( Index idx, bool state )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Favorite != state )
|
||||
{
|
||||
mod.Favorite = state;
|
||||
mod.SaveLocalData();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Favorite, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModNote( Index idx, string newNote )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Note != newNote )
|
||||
{
|
||||
mod.Note = newNote;
|
||||
mod.SaveLocalData();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Favorite, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ChangeTag( Index idx, int tagIdx, string newTag, bool local )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
var which = local ? mod.LocalTags : mod.ModTags;
|
||||
if( tagIdx < 0 || tagIdx > which.Count )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ModDataChangeType flags = 0;
|
||||
if( tagIdx == which.Count )
|
||||
{
|
||||
flags = mod.UpdateTags( local ? null : which.Append( newTag ), local ? which.Append( newTag ) : null );
|
||||
}
|
||||
else
|
||||
{
|
||||
var tmp = which.ToArray();
|
||||
tmp[ tagIdx ] = newTag;
|
||||
flags = mod.UpdateTags( local ? null : tmp, local ? tmp : null );
|
||||
}
|
||||
|
||||
if( flags.HasFlag( ModDataChangeType.ModTags ) )
|
||||
{
|
||||
mod.SaveMeta();
|
||||
}
|
||||
|
||||
if( flags.HasFlag( ModDataChangeType.LocalTags ) )
|
||||
{
|
||||
mod.SaveLocalData();
|
||||
}
|
||||
|
||||
if( flags != 0 )
|
||||
{
|
||||
ModDataChanged?.Invoke( flags, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeLocalTag( Index idx, int tagIdx, string newTag )
|
||||
=> ChangeTag( idx, tagIdx, newTag, true );
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public sealed partial class Mod
|
||||
{
|
||||
public partial class Manager
|
||||
{
|
||||
public delegate void ModDataChangeDelegate( ModDataChangeType type, Mod mod, string? oldName );
|
||||
public event ModDataChangeDelegate? ModDataChanged;
|
||||
|
||||
public void ChangeModName( Index idx, string newName )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Name.Text != newName )
|
||||
{
|
||||
var oldName = mod.Name;
|
||||
mod.Name = newName;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Name, mod, oldName.Text );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModAuthor( Index idx, string newAuthor )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Author != newAuthor )
|
||||
{
|
||||
mod.Author = newAuthor;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Author, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModDescription( Index idx, string newDescription )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Description != newDescription )
|
||||
{
|
||||
mod.Description = newDescription;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Description, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModVersion( Index idx, string newVersion )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Version != newVersion )
|
||||
{
|
||||
mod.Version = newVersion;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Version, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModWebsite( Index idx, string newWebsite )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( mod.Website != newWebsite )
|
||||
{
|
||||
mod.Website = newWebsite;
|
||||
mod.SaveMeta();
|
||||
ModDataChanged?.Invoke( ModDataChangeType.Website, mod, null );
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModTag( Index idx, int tagIdx, string newTag )
|
||||
=> ChangeTag( idx, tagIdx, newTag, false );
|
||||
}
|
||||
}
|
||||
|
|
@ -305,18 +305,18 @@ public sealed partial class Mod
|
|||
public bool VerifyFileName(Mod mod, IModGroup? group, string newName, bool message)
|
||||
{
|
||||
var path = newName.RemoveInvalidPathSymbols();
|
||||
if (path.Length == 0
|
||||
|| mod.Groups.Any(o => !ReferenceEquals(o, group)
|
||||
if (path.Length != 0
|
||||
&& !mod.Groups.Any(o => !ReferenceEquals(o, group)
|
||||
&& string.Equals(o.Name.RemoveInvalidPathSymbols(), path, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (message)
|
||||
_chat.NotificationMessage($"Could not name option {newName} because option with same filename {path} already exists.",
|
||||
"Warning", NotificationType.Warning);
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
if (message)
|
||||
Penumbra.ChatService.NotificationMessage(
|
||||
$"Could not name option {newName} because option with same filename {path} already exists.",
|
||||
"Warning", NotificationType.Warning);
|
||||
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static SubMod GetSubMod(Mod mod, int groupIdx, int optionIdx)
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public sealed partial class Mod
|
|||
var queue = new ConcurrentQueue< Mod >();
|
||||
Parallel.ForEach( BasePath.EnumerateDirectories(), options, dir =>
|
||||
{
|
||||
var mod = LoadMod( dir, false );
|
||||
var mod = LoadMod( this, dir, false );
|
||||
if( mod != null )
|
||||
{
|
||||
queue.Enqueue( mod );
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
|
@ -36,14 +37,16 @@ public sealed partial class Mod
|
|||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly ChatService _chat;
|
||||
private readonly Configuration _config;
|
||||
private readonly CommunicatorService _communicator;
|
||||
public readonly ModDataEditor DataEditor;
|
||||
|
||||
public Manager(StartTracker time, Configuration config, ChatService chat)
|
||||
public Manager(StartTracker time, Configuration config, CommunicatorService communicator, ModDataEditor dataEditor)
|
||||
{
|
||||
using var timer = time.Measure(StartTimeType.Mods);
|
||||
_config = config;
|
||||
_chat = chat;
|
||||
_communicator = communicator;
|
||||
DataEditor = dataEditor;
|
||||
ModDirectoryChanged += OnModDirectoryChange;
|
||||
SetBaseDirectory(config.ModDirectory, true);
|
||||
UpdateExportDirectory(_config.ExportDirectory, false);
|
||||
|
|
|
|||
21
Penumbra/Mods/Manager/ModDataChangeType.cs
Normal file
21
Penumbra/Mods/Manager/ModDataChangeType.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
[Flags]
|
||||
public enum ModDataChangeType : ushort
|
||||
{
|
||||
None = 0x0000,
|
||||
Name = 0x0001,
|
||||
Author = 0x0002,
|
||||
Description = 0x0004,
|
||||
Version = 0x0008,
|
||||
Website = 0x0010,
|
||||
Deletion = 0x0020,
|
||||
Migration = 0x0040,
|
||||
ModTags = 0x0080,
|
||||
ImportDate = 0x0100,
|
||||
Favorite = 0x0200,
|
||||
LocalTags = 0x0400,
|
||||
Note = 0x0800,
|
||||
}
|
||||
362
Penumbra/Mods/Manager/ModDataEditor.cs
Normal file
362
Penumbra/Mods/Manager/ModDataEditor.cs
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public class ModDataEditor
|
||||
{
|
||||
private readonly FilenameService _filenameService;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly CommunicatorService _communicatorService;
|
||||
|
||||
public ModDataEditor(FilenameService filenameService, SaveService saveService, CommunicatorService communicatorService)
|
||||
{
|
||||
_filenameService = filenameService;
|
||||
_saveService = saveService;
|
||||
_communicatorService = communicatorService;
|
||||
}
|
||||
|
||||
public string MetaFile(Mod mod)
|
||||
=> _filenameService.ModMetaPath(mod);
|
||||
|
||||
public string DataFile(Mod mod)
|
||||
=> _filenameService.LocalDataFile(mod);
|
||||
|
||||
/// <summary> Create the file containing the meta information about a mod from scratch. </summary>
|
||||
public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version,
|
||||
string? website)
|
||||
{
|
||||
var mod = new Mod(directory);
|
||||
mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString(name!);
|
||||
mod.Author = author != null ? new LowerString(author) : mod.Author;
|
||||
mod.Description = description ?? mod.Description;
|
||||
mod.Version = version ?? mod.Version;
|
||||
mod.Website = website ?? mod.Website;
|
||||
_saveService.ImmediateSave(new ModMeta(mod));
|
||||
}
|
||||
|
||||
public ModDataChangeType LoadLocalData(Mod mod)
|
||||
{
|
||||
var dataFile = _filenameService.LocalDataFile(mod);
|
||||
|
||||
var importDate = 0L;
|
||||
var localTags = Enumerable.Empty<string>();
|
||||
var favorite = false;
|
||||
var note = string.Empty;
|
||||
|
||||
var save = true;
|
||||
if (File.Exists(dataFile))
|
||||
{
|
||||
save = false;
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(dataFile);
|
||||
var json = JObject.Parse(text);
|
||||
|
||||
importDate = json[nameof(Mod.ImportDate)]?.Value<long>() ?? importDate;
|
||||
favorite = json[nameof(Mod.Favorite)]?.Value<bool>() ?? favorite;
|
||||
note = json[nameof(Mod.Note)]?.Value<string>() ?? note;
|
||||
localTags = json[nameof(Mod.LocalTags)]?.Values<string>().OfType<string>() ?? localTags;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not load local mod data:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
if (importDate == 0)
|
||||
importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if (mod.ImportDate != importDate)
|
||||
{
|
||||
mod.ImportDate = importDate;
|
||||
changes |= ModDataChangeType.ImportDate;
|
||||
}
|
||||
|
||||
changes |= mod.UpdateTags(null, localTags);
|
||||
|
||||
if (mod.Favorite != favorite)
|
||||
{
|
||||
mod.Favorite = favorite;
|
||||
changes |= ModDataChangeType.Favorite;
|
||||
}
|
||||
|
||||
if (mod.Note != note)
|
||||
{
|
||||
mod.Note = note;
|
||||
changes |= ModDataChangeType.Note;
|
||||
}
|
||||
|
||||
if (save)
|
||||
_saveService.QueueSave(new ModData(mod));
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public ModDataChangeType LoadMeta(Mod mod)
|
||||
{
|
||||
var metaFile = _filenameService.ModMetaPath(mod);
|
||||
if (!File.Exists(metaFile))
|
||||
{
|
||||
Penumbra.Log.Debug($"No mod meta found for {mod.ModPath.Name}.");
|
||||
return ModDataChangeType.Deletion;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(metaFile);
|
||||
var json = JObject.Parse(text);
|
||||
|
||||
var newName = json[nameof(Mod.Name)]?.Value<string>() ?? string.Empty;
|
||||
var newAuthor = json[nameof(Mod.Author)]?.Value<string>() ?? string.Empty;
|
||||
var newDescription = json[nameof(Mod.Description)]?.Value<string>() ?? string.Empty;
|
||||
var newVersion = json[nameof(Mod.Version)]?.Value<string>() ?? string.Empty;
|
||||
var newWebsite = json[nameof(Mod.Website)]?.Value<string>() ?? string.Empty;
|
||||
var newFileVersion = json[nameof(Mod.FileVersion)]?.Value<uint>() ?? 0;
|
||||
var importDate = json[nameof(Mod.ImportDate)]?.Value<long>();
|
||||
var modTags = json[nameof(Mod.ModTags)]?.Values<string>().OfType<string>();
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if (mod.Name != newName)
|
||||
{
|
||||
changes |= ModDataChangeType.Name;
|
||||
mod.Name = newName;
|
||||
}
|
||||
|
||||
if (mod.Author != newAuthor)
|
||||
{
|
||||
changes |= ModDataChangeType.Author;
|
||||
mod.Author = newAuthor;
|
||||
}
|
||||
|
||||
if (mod.Description != newDescription)
|
||||
{
|
||||
changes |= ModDataChangeType.Description;
|
||||
mod.Description = newDescription;
|
||||
}
|
||||
|
||||
if (mod.Version != newVersion)
|
||||
{
|
||||
changes |= ModDataChangeType.Version;
|
||||
mod.Version = newVersion;
|
||||
}
|
||||
|
||||
if (mod.Website != newWebsite)
|
||||
{
|
||||
changes |= ModDataChangeType.Website;
|
||||
mod.Website = newWebsite;
|
||||
}
|
||||
|
||||
if (mod.FileVersion != newFileVersion)
|
||||
{
|
||||
mod.FileVersion = newFileVersion;
|
||||
if (Mod.Migration.Migrate(mod, json))
|
||||
{
|
||||
changes |= ModDataChangeType.Migration;
|
||||
_saveService.ImmediateSave(new ModMeta(mod));
|
||||
}
|
||||
}
|
||||
|
||||
if (importDate != null && mod.ImportDate != importDate.Value)
|
||||
{
|
||||
mod.ImportDate = importDate.Value;
|
||||
changes |= ModDataChangeType.ImportDate;
|
||||
}
|
||||
|
||||
changes |= mod.UpdateTags(modTags, null);
|
||||
|
||||
return changes;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not load mod meta:\n{e}");
|
||||
return ModDataChangeType.Deletion;
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeModName(Mod mod, string newName)
|
||||
{
|
||||
if (mod.Name.Text == newName)
|
||||
return;
|
||||
|
||||
var oldName = mod.Name;
|
||||
mod.Name = newName;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Name, mod, oldName.Text);
|
||||
}
|
||||
|
||||
public void ChangeModAuthor(Mod mod, string newAuthor)
|
||||
{
|
||||
if (mod.Author == newAuthor)
|
||||
return;
|
||||
|
||||
mod.Author = newAuthor;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Author, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModDescription(Mod mod, string newDescription)
|
||||
{
|
||||
if (mod.Description == newDescription)
|
||||
return;
|
||||
|
||||
mod.Description = newDescription;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Description, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModVersion(Mod mod, string newVersion)
|
||||
{
|
||||
if (mod.Version == newVersion)
|
||||
return;
|
||||
|
||||
mod.Version = newVersion;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Version, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModWebsite(Mod mod, string newWebsite)
|
||||
{
|
||||
if (mod.Website == newWebsite)
|
||||
return;
|
||||
|
||||
mod.Website = newWebsite;
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Website, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModTag(Mod mod, int tagIdx, string newTag)
|
||||
=> ChangeTag(mod, tagIdx, newTag, false);
|
||||
|
||||
public void ChangeLocalTag(Mod mod, int tagIdx, string newTag)
|
||||
=> ChangeTag(mod, tagIdx, newTag, true);
|
||||
|
||||
public void ChangeModFavorite(Mod mod, bool state)
|
||||
{
|
||||
if (mod.Favorite == state)
|
||||
return;
|
||||
|
||||
mod.Favorite = state;
|
||||
_saveService.QueueSave(new ModData(mod));
|
||||
;
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModNote(Mod mod, string newNote)
|
||||
{
|
||||
if (mod.Note == newNote)
|
||||
return;
|
||||
|
||||
mod.Note = newNote;
|
||||
_saveService.QueueSave(new ModData(mod));
|
||||
;
|
||||
_communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null);
|
||||
}
|
||||
|
||||
|
||||
private void ChangeTag(Mod mod, int tagIdx, string newTag, bool local)
|
||||
{
|
||||
var which = local ? mod.LocalTags : mod.ModTags;
|
||||
if (tagIdx < 0 || tagIdx > which.Count)
|
||||
return;
|
||||
|
||||
ModDataChangeType flags = 0;
|
||||
if (tagIdx == which.Count)
|
||||
{
|
||||
flags = mod.UpdateTags(local ? null : which.Append(newTag), local ? which.Append(newTag) : null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tmp = which.ToArray();
|
||||
tmp[tagIdx] = newTag;
|
||||
flags = mod.UpdateTags(local ? null : tmp, local ? tmp : null);
|
||||
}
|
||||
|
||||
if (flags.HasFlag(ModDataChangeType.ModTags))
|
||||
_saveService.QueueSave(new ModMeta(mod));
|
||||
|
||||
if (flags.HasFlag(ModDataChangeType.LocalTags))
|
||||
_saveService.QueueSave(new ModData(mod));
|
||||
|
||||
if (flags != 0)
|
||||
_communicatorService.ModDataChanged.Invoke(flags, mod, null);
|
||||
}
|
||||
|
||||
public void MoveDataFile(DirectoryInfo oldMod, DirectoryInfo newMod)
|
||||
{
|
||||
var oldFile = _filenameService.LocalDataFile(oldMod.Name);
|
||||
var newFile = _filenameService.LocalDataFile(newMod.Name);
|
||||
if (!File.Exists(oldFile))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(oldFile, newFile, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not move local data file {oldFile} to {newFile}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly struct ModMeta : ISavable
|
||||
{
|
||||
private readonly Mod _mod;
|
||||
|
||||
public ModMeta(Mod mod)
|
||||
=> _mod = mod;
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.ModMetaPath(_mod);
|
||||
|
||||
public void Save(StreamWriter writer)
|
||||
{
|
||||
var jObject = new JObject
|
||||
{
|
||||
{ nameof(Mod.FileVersion), JToken.FromObject(_mod.FileVersion) },
|
||||
{ nameof(Mod.Name), JToken.FromObject(_mod.Name) },
|
||||
{ nameof(Mod.Author), JToken.FromObject(_mod.Author) },
|
||||
{ nameof(Mod.Description), JToken.FromObject(_mod.Description) },
|
||||
{ nameof(Mod.Version), JToken.FromObject(_mod.Version) },
|
||||
{ nameof(Mod.Website), JToken.FromObject(_mod.Website) },
|
||||
{ nameof(Mod.ModTags), JToken.FromObject(_mod.ModTags) },
|
||||
};
|
||||
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||
jObject.WriteTo(jWriter);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct ModData : ISavable
|
||||
{
|
||||
private readonly Mod _mod;
|
||||
|
||||
public ModData(Mod mod)
|
||||
=> _mod = mod;
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.LocalDataFile(_mod);
|
||||
|
||||
public void Save(StreamWriter writer)
|
||||
{
|
||||
var jObject = new JObject
|
||||
{
|
||||
{ nameof(Mod.FileVersion), JToken.FromObject(_mod.FileVersion) },
|
||||
{ nameof(Mod.ImportDate), JToken.FromObject(_mod.ImportDate) },
|
||||
{ nameof(Mod.LocalTags), JToken.FromObject(_mod.LocalTags) },
|
||||
{ nameof(Mod.Note), JToken.FromObject(_mod.Note) },
|
||||
{ nameof(Mod.Favorite), JToken.FromObject(_mod.Favorite) },
|
||||
};
|
||||
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||
jObject.WriteTo(jWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ public enum ModPathChangeType
|
|||
public partial class Mod
|
||||
{
|
||||
public DirectoryInfo ModPath { get; private set; }
|
||||
public string Identifier
|
||||
=> Index >= 0 ? ModPath.Name : Name;
|
||||
public int Index { get; private set; } = -1;
|
||||
|
||||
public bool IsTemporary
|
||||
|
|
@ -31,7 +33,7 @@ public partial class Mod
|
|||
_default = new SubMod( this );
|
||||
}
|
||||
|
||||
private static Mod? LoadMod( DirectoryInfo modPath, bool incorporateMetaChanges )
|
||||
private static Mod? LoadMod( Manager modManager, DirectoryInfo modPath, bool incorporateMetaChanges )
|
||||
{
|
||||
modPath.Refresh();
|
||||
if( !modPath.Exists )
|
||||
|
|
@ -40,18 +42,17 @@ public partial class Mod
|
|||
return null;
|
||||
}
|
||||
|
||||
var mod = new Mod( modPath );
|
||||
if( !mod.Reload( incorporateMetaChanges, out _ ) )
|
||||
{
|
||||
// Can not be base path not existing because that is checked before.
|
||||
Penumbra.Log.Warning( $"Mod at {modPath} without name is not supported." );
|
||||
return null;
|
||||
}
|
||||
var mod = new Mod(modPath);
|
||||
if (mod.Reload(modManager, incorporateMetaChanges, out _))
|
||||
return mod;
|
||||
|
||||
// Can not be base path not existing because that is checked before.
|
||||
Penumbra.Log.Warning( $"Mod at {modPath} without name is not supported." );
|
||||
return null;
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
internal bool Reload( bool incorporateMetaChanges, out ModDataChangeType modDataChange )
|
||||
internal bool Reload(Manager modManager, bool incorporateMetaChanges, out ModDataChangeType modDataChange )
|
||||
{
|
||||
modDataChange = ModDataChangeType.Deletion;
|
||||
ModPath.Refresh();
|
||||
|
|
@ -60,19 +61,19 @@ public partial class Mod
|
|||
return false;
|
||||
}
|
||||
|
||||
modDataChange = LoadMeta();
|
||||
modDataChange = modManager.DataEditor.LoadMeta(this);
|
||||
if( modDataChange.HasFlag( ModDataChangeType.Deletion ) || Name.Length == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LoadLocalData();
|
||||
modManager.DataEditor.LoadLocalData(this);
|
||||
|
||||
LoadDefaultOption();
|
||||
LoadAllGroups();
|
||||
if( incorporateMetaChanges )
|
||||
{
|
||||
IncorporateAllMetaChanges( true );
|
||||
IncorporateAllMetaChanges(true);
|
||||
}
|
||||
|
||||
ComputeChangedItems();
|
||||
|
|
|
|||
|
|
@ -64,19 +64,6 @@ public partial class Mod
|
|||
return newModFolder.Length == 0 ? null : new DirectoryInfo( newModFolder );
|
||||
}
|
||||
|
||||
/// <summary> Create the file containing the meta information about a mod from scratch. </summary>
|
||||
public static void CreateMeta( DirectoryInfo directory, string? name, string? author, string? description, string? version,
|
||||
string? website )
|
||||
{
|
||||
var mod = new Mod( directory );
|
||||
mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString( name! );
|
||||
mod.Author = author != null ? new LowerString( author ) : mod.Author;
|
||||
mod.Description = description ?? mod.Description;
|
||||
mod.Version = version ?? mod.Version;
|
||||
mod.Website = website ?? mod.Website;
|
||||
mod.SaveMetaFile(); // Not delayed.
|
||||
}
|
||||
|
||||
/// <summary> Create a file for an option group from given data. </summary>
|
||||
public static void CreateOptionGroup( DirectoryInfo baseFolder, GroupType type, string name,
|
||||
int priority, int index, uint defaultSettings, string desc, IEnumerable< ISubMod > subMods )
|
||||
|
|
@ -147,13 +134,11 @@ public partial class Mod
|
|||
internal static void CreateDefaultFiles( DirectoryInfo directory )
|
||||
{
|
||||
var mod = new Mod( directory );
|
||||
mod.Reload( false, out _ );
|
||||
mod.Reload( Penumbra.ModManager, false, out _ );
|
||||
foreach( var file in mod.FindUnusedFiles() )
|
||||
{
|
||||
if( Utf8GamePath.FromFile( new FileInfo( file.FullName ), directory, out var gamePath, true ) )
|
||||
{
|
||||
mod._default.FileData.TryAdd( gamePath, file );
|
||||
}
|
||||
}
|
||||
|
||||
mod._default.IncorporateMetaChanges( directory, true );
|
||||
|
|
|
|||
|
|
@ -3,145 +3,30 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Services;
|
||||
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public sealed partial class Mod
|
||||
{
|
||||
public static DirectoryInfo LocalDataDirectory(DalamudPluginInterface pi)
|
||||
=> new(Path.Combine( pi.ConfigDirectory.FullName, "mod_data" ));
|
||||
public long ImportDate { get; internal set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
|
||||
|
||||
public long ImportDate { get; private set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
|
||||
public IReadOnlyList<string> LocalTags { get; private set; } = Array.Empty<string>();
|
||||
|
||||
public IReadOnlyList< string > LocalTags { get; private set; } = Array.Empty< string >();
|
||||
public string AllTagsLower { get; private set; } = string.Empty;
|
||||
public string Note { get; internal set; } = string.Empty;
|
||||
public bool Favorite { get; internal set; } = false;
|
||||
|
||||
public string AllTagsLower { get; private set; } = string.Empty;
|
||||
public string Note { get; private set; } = string.Empty;
|
||||
public bool Favorite { get; private set; } = false;
|
||||
|
||||
private FileInfo LocalDataFile
|
||||
=> new(Path.Combine( DalamudServices.PluginInterface.ConfigDirectory.FullName, "mod_data", $"{ModPath.Name}.json" ));
|
||||
|
||||
private ModDataChangeType LoadLocalData()
|
||||
internal ModDataChangeType UpdateTags(IEnumerable<string>? newModTags, IEnumerable<string>? newLocalTags)
|
||||
{
|
||||
var dataFile = LocalDataFile;
|
||||
|
||||
var importDate = 0L;
|
||||
var localTags = Enumerable.Empty< string >();
|
||||
var favorite = false;
|
||||
var note = string.Empty;
|
||||
|
||||
var save = true;
|
||||
if( File.Exists( dataFile.FullName ) )
|
||||
{
|
||||
save = false;
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText( dataFile.FullName );
|
||||
var json = JObject.Parse( text );
|
||||
|
||||
importDate = json[ nameof( ImportDate ) ]?.Value< long >() ?? importDate;
|
||||
favorite = json[ nameof( Favorite ) ]?.Value< bool >() ?? favorite;
|
||||
note = json[ nameof( Note ) ]?.Value< string >() ?? note;
|
||||
localTags = json[ nameof( LocalTags ) ]?.Values< string >().OfType< string >() ?? localTags;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not load local mod data:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
if( importDate == 0 )
|
||||
{
|
||||
importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if( ImportDate != importDate )
|
||||
{
|
||||
ImportDate = importDate;
|
||||
changes |= ModDataChangeType.ImportDate;
|
||||
}
|
||||
|
||||
changes |= UpdateTags( null, localTags );
|
||||
|
||||
if( Favorite != favorite )
|
||||
{
|
||||
Favorite = favorite;
|
||||
changes |= ModDataChangeType.Favorite;
|
||||
}
|
||||
|
||||
if( Note != note )
|
||||
{
|
||||
Note = note;
|
||||
changes |= ModDataChangeType.Note;
|
||||
}
|
||||
|
||||
if( save )
|
||||
{
|
||||
SaveLocalDataFile();
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private void SaveLocalData()
|
||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveLocalData ) + ModPath.Name, SaveLocalDataFile );
|
||||
|
||||
private void SaveLocalDataFile()
|
||||
{
|
||||
var dataFile = LocalDataFile;
|
||||
try
|
||||
{
|
||||
var jObject = new JObject
|
||||
{
|
||||
{ nameof( FileVersion ), JToken.FromObject( FileVersion ) },
|
||||
{ nameof( ImportDate ), JToken.FromObject( ImportDate ) },
|
||||
{ nameof( LocalTags ), JToken.FromObject( LocalTags ) },
|
||||
{ nameof( Note ), JToken.FromObject( Note ) },
|
||||
{ nameof( Favorite ), JToken.FromObject( Favorite ) },
|
||||
};
|
||||
dataFile.Directory!.Create();
|
||||
File.WriteAllText( dataFile.FullName, jObject.ToString( Formatting.Indented ) );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not write local data file for mod {Name} to {dataFile.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
private static void MoveDataFile( DirectoryInfo oldMod, DirectoryInfo newMod )
|
||||
{
|
||||
var oldFile = Path.Combine( DalamudServices.PluginInterface.ConfigDirectory.FullName, "mod_data", $"{oldMod.Name}.json" );
|
||||
var newFile = Path.Combine( DalamudServices.PluginInterface.ConfigDirectory.FullName, "mod_data", $"{newMod.Name}.json" );
|
||||
if( File.Exists( oldFile ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Move( oldFile, newFile, true );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not move local data file {oldFile} to {newFile}:\n{e}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ModDataChangeType UpdateTags( IEnumerable< string >? newModTags, IEnumerable< string >? newLocalTags )
|
||||
{
|
||||
if( newModTags == null && newLocalTags == null )
|
||||
{
|
||||
if (newModTags == null && newLocalTags == null)
|
||||
return 0;
|
||||
}
|
||||
|
||||
ModDataChangeType type = 0;
|
||||
if( newModTags != null )
|
||||
if (newModTags != null)
|
||||
{
|
||||
var modTags = newModTags.Where( t => t.Length > 0 ).Distinct().ToArray();
|
||||
if( !modTags.SequenceEqual( ModTags ) )
|
||||
var modTags = newModTags.Where(t => t.Length > 0).Distinct().ToArray();
|
||||
if (!modTags.SequenceEqual(ModTags))
|
||||
{
|
||||
newLocalTags ??= LocalTags;
|
||||
ModTags = modTags;
|
||||
|
|
@ -149,21 +34,19 @@ public sealed partial class Mod
|
|||
}
|
||||
}
|
||||
|
||||
if( newLocalTags != null )
|
||||
if (newLocalTags != null)
|
||||
{
|
||||
var localTags = newLocalTags!.Where( t => t.Length > 0 && !ModTags.Contains( t ) ).Distinct().ToArray();
|
||||
if( !localTags.SequenceEqual( LocalTags ) )
|
||||
var localTags = newLocalTags!.Where(t => t.Length > 0 && !ModTags.Contains(t)).Distinct().ToArray();
|
||||
if (!localTags.SequenceEqual(LocalTags))
|
||||
{
|
||||
LocalTags = localTags;
|
||||
type |= ModDataChangeType.LocalTags;
|
||||
}
|
||||
}
|
||||
|
||||
if( type != 0 )
|
||||
{
|
||||
AllTagsLower = string.Join( '\0', ModTags.Concat( LocalTags ).Select( s => s.ToLowerInvariant() ) );
|
||||
}
|
||||
if (type != 0)
|
||||
AllTagsLower = string.Join('\0', ModTags.Concat(LocalTags).Select(s => s.ToLowerInvariant()));
|
||||
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,146 +13,115 @@ namespace Penumbra.Mods;
|
|||
|
||||
public sealed partial class Mod
|
||||
{
|
||||
private static class Migration
|
||||
public static partial class Migration
|
||||
{
|
||||
public static bool Migrate( Mod mod, JObject json )
|
||||
{
|
||||
var ret = MigrateV0ToV1( mod, json ) || MigrateV1ToV2( mod ) || MigrateV2ToV3( mod );
|
||||
if( ret )
|
||||
{
|
||||
// Immediately save on migration.
|
||||
mod.SaveMetaFile();
|
||||
}
|
||||
public static bool Migrate(Mod mod, JObject json)
|
||||
=> MigrateV0ToV1(mod, json) || MigrateV1ToV2(mod) || MigrateV2ToV3(mod);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool MigrateV2ToV3( Mod mod )
|
||||
private static bool MigrateV2ToV3(Mod mod)
|
||||
{
|
||||
if( mod.FileVersion > 2 )
|
||||
{
|
||||
if (mod.FileVersion > 2)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove import time.
|
||||
mod.FileVersion = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"group_\d{3}_", RegexOptions.Compiled | RegexOptions.NonBacktracking | RegexOptions.ExplicitCapture)]
|
||||
private static partial Regex GroupRegex();
|
||||
|
||||
private static readonly Regex GroupRegex = new( @"group_\d{3}_", RegexOptions.Compiled );
|
||||
private static bool MigrateV1ToV2( Mod mod )
|
||||
private static bool MigrateV1ToV2(Mod mod)
|
||||
{
|
||||
if( mod.FileVersion > 1 )
|
||||
{
|
||||
if (mod.FileVersion > 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mod.GroupFiles.All( g => GroupRegex.IsMatch( g.Name )))
|
||||
{
|
||||
foreach( var (group, index) in mod.GroupFiles.WithIndex().ToArray() )
|
||||
if (!mod.GroupFiles.All(g => GroupRegex().IsMatch(g.Name)))
|
||||
foreach (var (group, index) in mod.GroupFiles.WithIndex().ToArray())
|
||||
{
|
||||
var newName = Regex.Replace( group.Name, "^group_", $"group_{index + 1:D3}_", RegexOptions.Compiled );
|
||||
var newName = Regex.Replace(group.Name, "^group_", $"group_{index + 1:D3}_", RegexOptions.Compiled);
|
||||
try
|
||||
{
|
||||
if( newName != group.Name )
|
||||
{
|
||||
group.MoveTo( Path.Combine( group.DirectoryName ?? string.Empty, newName ), false );
|
||||
}
|
||||
if (newName != group.Name)
|
||||
group.MoveTo(Path.Combine(group.DirectoryName ?? string.Empty, newName), false);
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not rename group file {group.Name} to {newName} during migration:\n{e}" );
|
||||
Penumbra.Log.Error($"Could not rename group file {group.Name} to {newName} during migration:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod.FileVersion = 2;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool MigrateV0ToV1( Mod mod, JObject json )
|
||||
private static bool MigrateV0ToV1(Mod mod, JObject json)
|
||||
{
|
||||
if( mod.FileVersion > 0 )
|
||||
{
|
||||
if (mod.FileVersion > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
var swaps = json[ "FileSwaps" ]?.ToObject< Dictionary< Utf8GamePath, FullPath > >()
|
||||
?? new Dictionary< Utf8GamePath, FullPath >();
|
||||
var groups = json[ "Groups" ]?.ToObject< Dictionary< string, OptionGroupV0 > >() ?? new Dictionary< string, OptionGroupV0 >();
|
||||
var swaps = json["FileSwaps"]?.ToObject<Dictionary<Utf8GamePath, FullPath>>()
|
||||
?? new Dictionary<Utf8GamePath, FullPath>();
|
||||
var groups = json["Groups"]?.ToObject<Dictionary<string, OptionGroupV0>>() ?? new Dictionary<string, OptionGroupV0>();
|
||||
var priority = 1;
|
||||
var seenMetaFiles = new HashSet< FullPath >();
|
||||
foreach( var group in groups.Values )
|
||||
{
|
||||
ConvertGroup( mod, group, ref priority, seenMetaFiles );
|
||||
}
|
||||
var seenMetaFiles = new HashSet<FullPath>();
|
||||
foreach (var group in groups.Values)
|
||||
ConvertGroup(mod, group, ref priority, seenMetaFiles);
|
||||
|
||||
foreach( var unusedFile in mod.FindUnusedFiles().Where( f => !seenMetaFiles.Contains( f ) ) )
|
||||
foreach (var unusedFile in mod.FindUnusedFiles().Where(f => !seenMetaFiles.Contains(f)))
|
||||
{
|
||||
if( unusedFile.ToGamePath( mod.ModPath, out var gamePath )
|
||||
&& !mod._default.FileData.TryAdd( gamePath, unusedFile ) )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not add {gamePath} because it already points to {mod._default.FileData[ gamePath ]}." );
|
||||
}
|
||||
if (unusedFile.ToGamePath(mod.ModPath, out var gamePath)
|
||||
&& !mod._default.FileData.TryAdd(gamePath, unusedFile))
|
||||
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod._default.FileData[gamePath]}.");
|
||||
}
|
||||
|
||||
mod._default.FileSwapData.Clear();
|
||||
mod._default.FileSwapData.EnsureCapacity( swaps.Count );
|
||||
foreach( var (gamePath, swapPath) in swaps )
|
||||
{
|
||||
mod._default.FileSwapData.Add( gamePath, swapPath );
|
||||
}
|
||||
mod._default.FileSwapData.EnsureCapacity(swaps.Count);
|
||||
foreach (var (gamePath, swapPath) in swaps)
|
||||
mod._default.FileSwapData.Add(gamePath, swapPath);
|
||||
|
||||
mod._default.IncorporateMetaChanges( mod.ModPath, true );
|
||||
foreach( var (group, index) in mod.Groups.WithIndex() )
|
||||
{
|
||||
IModGroup.Save( group, mod.ModPath, index );
|
||||
}
|
||||
mod._default.IncorporateMetaChanges(mod.ModPath, true);
|
||||
foreach (var (group, index) in mod.Groups.WithIndex())
|
||||
IModGroup.Save(group, mod.ModPath, index);
|
||||
|
||||
// Delete meta files.
|
||||
foreach( var file in seenMetaFiles.Where( f => f.Exists ) )
|
||||
foreach (var file in seenMetaFiles.Where(f => f.Exists))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete( file.FullName );
|
||||
File.Delete(file.FullName);
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Warning( $"Could not delete meta file {file.FullName} during migration:\n{e}" );
|
||||
Penumbra.Log.Warning($"Could not delete meta file {file.FullName} during migration:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
// Delete old meta files.
|
||||
var oldMetaFile = Path.Combine( mod.ModPath.FullName, "metadata_manipulations.json" );
|
||||
if( File.Exists( oldMetaFile ) )
|
||||
{
|
||||
var oldMetaFile = Path.Combine(mod.ModPath.FullName, "metadata_manipulations.json");
|
||||
if (File.Exists(oldMetaFile))
|
||||
try
|
||||
{
|
||||
File.Delete( oldMetaFile );
|
||||
File.Delete(oldMetaFile);
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Warning( $"Could not delete old meta file {oldMetaFile} during migration:\n{e}" );
|
||||
Penumbra.Log.Warning($"Could not delete old meta file {oldMetaFile} during migration:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
mod.FileVersion = 1;
|
||||
mod.SaveDefaultMod();
|
||||
mod.SaveMetaFile();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ConvertGroup( Mod mod, OptionGroupV0 group, ref int priority, HashSet< FullPath > seenMetaFiles )
|
||||
private static void ConvertGroup(Mod mod, OptionGroupV0 group, ref int priority, HashSet<FullPath> seenMetaFiles)
|
||||
{
|
||||
if( group.Options.Count == 0 )
|
||||
{
|
||||
if (group.Options.Count == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
switch( group.SelectionType )
|
||||
switch (group.SelectionType)
|
||||
{
|
||||
case GroupType.Multi:
|
||||
|
||||
|
|
@ -163,17 +132,15 @@ public sealed partial class Mod
|
|||
Priority = priority++,
|
||||
Description = string.Empty,
|
||||
};
|
||||
mod._groups.Add( newMultiGroup );
|
||||
foreach( var option in group.Options )
|
||||
{
|
||||
newMultiGroup.PrioritizedOptions.Add( ( SubModFromOption( mod, option, seenMetaFiles ), optionPriority++ ) );
|
||||
}
|
||||
mod._groups.Add(newMultiGroup);
|
||||
foreach (var option in group.Options)
|
||||
newMultiGroup.PrioritizedOptions.Add((SubModFromOption(mod, option, seenMetaFiles), optionPriority++));
|
||||
|
||||
break;
|
||||
case GroupType.Single:
|
||||
if( group.Options.Count == 1 )
|
||||
if (group.Options.Count == 1)
|
||||
{
|
||||
AddFilesToSubMod( mod._default, mod.ModPath, group.Options[ 0 ], seenMetaFiles );
|
||||
AddFilesToSubMod(mod._default, mod.ModPath, group.Options[0], seenMetaFiles);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -183,38 +150,32 @@ public sealed partial class Mod
|
|||
Priority = priority++,
|
||||
Description = string.Empty,
|
||||
};
|
||||
mod._groups.Add( newSingleGroup );
|
||||
foreach( var option in group.Options )
|
||||
{
|
||||
newSingleGroup.OptionData.Add( SubModFromOption( mod, option, seenMetaFiles ) );
|
||||
}
|
||||
mod._groups.Add(newSingleGroup);
|
||||
foreach (var option in group.Options)
|
||||
newSingleGroup.OptionData.Add(SubModFromOption(mod, option, seenMetaFiles));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddFilesToSubMod( SubMod mod, DirectoryInfo basePath, OptionV0 option, HashSet< FullPath > seenMetaFiles )
|
||||
private static void AddFilesToSubMod(SubMod mod, DirectoryInfo basePath, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||
{
|
||||
foreach( var (relPath, gamePaths) in option.OptionFiles )
|
||||
foreach (var (relPath, gamePaths) in option.OptionFiles)
|
||||
{
|
||||
var fullPath = new FullPath( basePath, relPath );
|
||||
foreach( var gamePath in gamePaths )
|
||||
{
|
||||
mod.FileData.TryAdd( gamePath, fullPath );
|
||||
}
|
||||
var fullPath = new FullPath(basePath, relPath);
|
||||
foreach (var gamePath in gamePaths)
|
||||
mod.FileData.TryAdd(gamePath, fullPath);
|
||||
|
||||
if( fullPath.Extension is ".meta" or ".rgsp" )
|
||||
{
|
||||
seenMetaFiles.Add( fullPath );
|
||||
}
|
||||
if (fullPath.Extension is ".meta" or ".rgsp")
|
||||
seenMetaFiles.Add(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static SubMod SubModFromOption( Mod mod, OptionV0 option, HashSet< FullPath > seenMetaFiles )
|
||||
private static SubMod SubModFromOption(Mod mod, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||
{
|
||||
var subMod = new SubMod( mod ) { Name = option.OptionName };
|
||||
AddFilesToSubMod( subMod, mod.ModPath, option, seenMetaFiles );
|
||||
subMod.IncorporateMetaChanges( mod.ModPath, false );
|
||||
var subMod = new SubMod(mod) { Name = option.OptionName };
|
||||
AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles);
|
||||
subMod.IncorporateMetaChanges(mod.ModPath, false);
|
||||
return subMod;
|
||||
}
|
||||
|
||||
|
|
@ -223,8 +184,8 @@ public sealed partial class Mod
|
|||
public string OptionName = string.Empty;
|
||||
public string OptionDesc = string.Empty;
|
||||
|
||||
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< Utf8GamePath > ) )]
|
||||
public Dictionary< Utf8RelPath, HashSet< Utf8GamePath > > OptionFiles = new();
|
||||
[JsonProperty(ItemConverterType = typeof(SingleOrArrayConverter<Utf8GamePath>))]
|
||||
public Dictionary<Utf8RelPath, HashSet<Utf8GamePath>> OptionFiles = new();
|
||||
|
||||
public OptionV0()
|
||||
{ }
|
||||
|
|
@ -234,53 +195,49 @@ public sealed partial class Mod
|
|||
{
|
||||
public string GroupName = string.Empty;
|
||||
|
||||
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public GroupType SelectionType = GroupType.Single;
|
||||
|
||||
public List< OptionV0 > Options = new();
|
||||
public List<OptionV0> Options = new();
|
||||
|
||||
public OptionGroupV0()
|
||||
{ }
|
||||
}
|
||||
|
||||
// Not used anymore, but required for migration.
|
||||
private class SingleOrArrayConverter< T > : JsonConverter
|
||||
private class SingleOrArrayConverter<T> : JsonConverter
|
||||
{
|
||||
public override bool CanConvert( Type objectType )
|
||||
=> objectType == typeof( HashSet< T > );
|
||||
public override bool CanConvert(Type objectType)
|
||||
=> objectType == typeof(HashSet<T>);
|
||||
|
||||
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var token = JToken.Load( reader );
|
||||
var token = JToken.Load(reader);
|
||||
|
||||
if( token.Type == JTokenType.Array )
|
||||
{
|
||||
return token.ToObject< HashSet< T > >() ?? new HashSet< T >();
|
||||
}
|
||||
if (token.Type == JTokenType.Array)
|
||||
return token.ToObject<HashSet<T>>() ?? new HashSet<T>();
|
||||
|
||||
var tmp = token.ToObject< T >();
|
||||
var tmp = token.ToObject<T>();
|
||||
return tmp != null
|
||||
? new HashSet< T > { tmp }
|
||||
: new HashSet< T >();
|
||||
? new HashSet<T> { tmp }
|
||||
: new HashSet<T>();
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
=> true;
|
||||
|
||||
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
if( value != null )
|
||||
if (value != null)
|
||||
{
|
||||
var v = ( HashSet< T > )value;
|
||||
foreach( var val in v )
|
||||
{
|
||||
serializer.Serialize( writer, val?.ToString() );
|
||||
}
|
||||
var v = (HashSet<T>)value;
|
||||
foreach (var val in v)
|
||||
serializer.Serialize(writer, val?.ToString());
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
[Flags]
|
||||
public enum ModDataChangeType : ushort
|
||||
{
|
||||
None = 0x0000,
|
||||
Name = 0x0001,
|
||||
Author = 0x0002,
|
||||
Description = 0x0004,
|
||||
Version = 0x0008,
|
||||
Website = 0x0010,
|
||||
Deletion = 0x0020,
|
||||
Migration = 0x0040,
|
||||
ModTags = 0x0080,
|
||||
ImportDate = 0x0100,
|
||||
Favorite = 0x0200,
|
||||
LocalTags = 0x0400,
|
||||
Note = 0x0800,
|
||||
}
|
||||
|
||||
public sealed partial class Mod : IMod
|
||||
{
|
||||
public static readonly TemporaryMod ForcedFiles = new()
|
||||
|
|
@ -36,122 +14,13 @@ public sealed partial class Mod : IMod
|
|||
};
|
||||
|
||||
public const uint CurrentFileVersion = 3;
|
||||
public uint FileVersion { get; private set; } = CurrentFileVersion;
|
||||
public LowerString Name { get; private set; } = "New Mod";
|
||||
public LowerString Author { get; private set; } = LowerString.Empty;
|
||||
public string Description { get; private set; } = string.Empty;
|
||||
public string Version { get; private set; } = string.Empty;
|
||||
public string Website { get; private set; } = string.Empty;
|
||||
public IReadOnlyList< string > ModTags { get; private set; } = Array.Empty< string >();
|
||||
|
||||
internal FileInfo MetaFile
|
||||
=> new(Path.Combine( ModPath.FullName, "meta.json" ));
|
||||
|
||||
private ModDataChangeType LoadMeta()
|
||||
{
|
||||
var metaFile = MetaFile;
|
||||
if( !File.Exists( metaFile.FullName ) )
|
||||
{
|
||||
Penumbra.Log.Debug( $"No mod meta found for {ModPath.Name}." );
|
||||
return ModDataChangeType.Deletion;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText( metaFile.FullName );
|
||||
var json = JObject.Parse( text );
|
||||
|
||||
var newName = json[ nameof( Name ) ]?.Value< string >() ?? string.Empty;
|
||||
var newAuthor = json[ nameof( Author ) ]?.Value< string >() ?? string.Empty;
|
||||
var newDescription = json[ nameof( Description ) ]?.Value< string >() ?? string.Empty;
|
||||
var newVersion = json[ nameof( Version ) ]?.Value< string >() ?? string.Empty;
|
||||
var newWebsite = json[ nameof( Website ) ]?.Value< string >() ?? string.Empty;
|
||||
var newFileVersion = json[ nameof( FileVersion ) ]?.Value< uint >() ?? 0;
|
||||
var importDate = json[ nameof( ImportDate ) ]?.Value< long >();
|
||||
var modTags = json[ nameof( ModTags ) ]?.Values< string >().OfType< string >();
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if( Name != newName )
|
||||
{
|
||||
changes |= ModDataChangeType.Name;
|
||||
Name = newName;
|
||||
}
|
||||
|
||||
if( Author != newAuthor )
|
||||
{
|
||||
changes |= ModDataChangeType.Author;
|
||||
Author = newAuthor;
|
||||
}
|
||||
|
||||
if( Description != newDescription )
|
||||
{
|
||||
changes |= ModDataChangeType.Description;
|
||||
Description = newDescription;
|
||||
}
|
||||
|
||||
if( Version != newVersion )
|
||||
{
|
||||
changes |= ModDataChangeType.Version;
|
||||
Version = newVersion;
|
||||
}
|
||||
|
||||
if( Website != newWebsite )
|
||||
{
|
||||
changes |= ModDataChangeType.Website;
|
||||
Website = newWebsite;
|
||||
}
|
||||
|
||||
if( FileVersion != newFileVersion )
|
||||
{
|
||||
FileVersion = newFileVersion;
|
||||
if( Migration.Migrate( this, json ) )
|
||||
{
|
||||
changes |= ModDataChangeType.Migration;
|
||||
}
|
||||
}
|
||||
|
||||
if( importDate != null && ImportDate != importDate.Value )
|
||||
{
|
||||
ImportDate = importDate.Value;
|
||||
changes |= ModDataChangeType.ImportDate;
|
||||
}
|
||||
|
||||
changes |= UpdateTags( modTags, null );
|
||||
|
||||
return changes;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not load mod meta:\n{e}" );
|
||||
return ModDataChangeType.Deletion;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveMeta()
|
||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveMetaFile ) + ModPath.Name, SaveMetaFile );
|
||||
|
||||
private void SaveMetaFile()
|
||||
{
|
||||
var metaFile = MetaFile;
|
||||
try
|
||||
{
|
||||
var jObject = new JObject
|
||||
{
|
||||
{ nameof( FileVersion ), JToken.FromObject( FileVersion ) },
|
||||
{ nameof( Name ), JToken.FromObject( Name ) },
|
||||
{ nameof( Author ), JToken.FromObject( Author ) },
|
||||
{ nameof( Description ), JToken.FromObject( Description ) },
|
||||
{ nameof( Version ), JToken.FromObject( Version ) },
|
||||
{ nameof( Website ), JToken.FromObject( Website ) },
|
||||
{ nameof( ModTags ), JToken.FromObject( ModTags ) },
|
||||
};
|
||||
File.WriteAllText( metaFile.FullName, jObject.ToString( Formatting.Indented ) );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not write meta file for mod {Name} to {metaFile.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
public uint FileVersion { get; internal set; } = CurrentFileVersion;
|
||||
public LowerString Name { get; internal set; } = "New Mod";
|
||||
public LowerString Author { get; internal set; } = LowerString.Empty;
|
||||
public string Description { get; internal set; } = string.Empty;
|
||||
public string Version { get; internal set; } = string.Empty;
|
||||
public string Website { get; internal set; } = string.Empty;
|
||||
public IReadOnlyList< string > ModTags { get; internal set; } = Array.Empty< string >();
|
||||
|
||||
public override string ToString()
|
||||
=> Name.Text;
|
||||
|
|
|
|||
|
|
@ -46,14 +46,14 @@ public sealed partial class Mod
|
|||
_default.ManipulationData = manips;
|
||||
}
|
||||
|
||||
public static void SaveTempCollection( ModCollection collection, string? character = null )
|
||||
public static void SaveTempCollection( Mod.Manager modManager, ModCollection collection, string? character = null )
|
||||
{
|
||||
DirectoryInfo? dir = null;
|
||||
try
|
||||
{
|
||||
dir = Creator.CreateModFolder( Penumbra.ModManager.BasePath, collection.Name );
|
||||
var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) );
|
||||
Creator.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor,
|
||||
modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor,
|
||||
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null );
|
||||
var mod = new Mod( dir );
|
||||
var defaultMod = mod._default;
|
||||
|
|
@ -88,7 +88,7 @@ public sealed partial class Mod
|
|||
}
|
||||
|
||||
mod.SaveDefaultMod();
|
||||
Penumbra.ModManager.AddMod( dir );
|
||||
modManager.AddMod( dir );
|
||||
Penumbra.Log.Information( $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}." );
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
|
|||
|
|
@ -10,28 +10,30 @@ using Penumbra.Util;
|
|||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISaveable
|
||||
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
|
||||
{
|
||||
private readonly Mod.Manager _modManager;
|
||||
private readonly FilenameService _files;
|
||||
private readonly Mod.Manager _modManager;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly FilenameService _files;
|
||||
|
||||
// Create a new ModFileSystem from the currently loaded mods and the current sort order file.
|
||||
public ModFileSystem(Mod.Manager modManager, FilenameService files)
|
||||
public ModFileSystem(Mod.Manager modManager, CommunicatorService communicator, FilenameService files)
|
||||
{
|
||||
_modManager = modManager;
|
||||
_files = files;
|
||||
_modManager = modManager;
|
||||
_communicator = communicator;
|
||||
_files = files;
|
||||
Reload();
|
||||
Changed += OnChange;
|
||||
_modManager.ModDiscoveryFinished += Reload;
|
||||
_modManager.ModDataChanged += OnDataChange;
|
||||
_modManager.ModPathChanged += OnModPathChange;
|
||||
Changed += OnChange;
|
||||
_modManager.ModDiscoveryFinished += Reload;
|
||||
_communicator.ModDataChanged.Event += OnDataChange;
|
||||
_modManager.ModPathChanged += OnModPathChange;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_modManager.ModPathChanged -= OnModPathChange;
|
||||
_modManager.ModDiscoveryFinished -= Reload;
|
||||
_modManager.ModDataChanged -= OnDataChange;
|
||||
_modManager.ModPathChanged -= OnModPathChange;
|
||||
_modManager.ModDiscoveryFinished -= Reload;
|
||||
_communicator.ModDataChanged.Event -= OnDataChange;
|
||||
}
|
||||
|
||||
public struct ImportDate : ISortMode<Mod>
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ public class PenumbraNew
|
|||
|
||||
// Add Mod Services
|
||||
services.AddSingleton<TempModManager>()
|
||||
.AddSingleton<ModDataEditor>()
|
||||
.AddSingleton<Mod.Manager>()
|
||||
.AddSingleton<ModFileSystem>();
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,13 @@ public class CommunicatorService : IDisposable
|
|||
/// </list> </summary>
|
||||
public readonly EventWrapper<nint, string, nint> CreatedCharacterBase = new(nameof(CreatedCharacterBase));
|
||||
|
||||
/// <summary><list type="number">
|
||||
/// <item>Parameter is the type of data change for the mod, which can be multiple flags. </item>
|
||||
/// <item>Parameter is the changed mod. </item>
|
||||
/// <item>Parameter is the old name of the mod in case of a name change, and null otherwise. </item>
|
||||
/// </list> </summary>
|
||||
public readonly EventWrapper<ModDataChangeType, Mod, string?> ModDataChanged = new(nameof(ModDataChanged));
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CollectionChange.Dispose();
|
||||
|
|
@ -52,5 +59,6 @@ public class CommunicatorService : IDisposable
|
|||
ModMetaChange.Dispose();
|
||||
CreatingCharacterBase.Dispose();
|
||||
CreatedCharacterBase.Dispose();
|
||||
ModDataChanged.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public class FilenameService
|
|||
|
||||
/// <summary> Obtain the path of the local data file given a mod directory. Returns an empty string if the mod is temporary. </summary>
|
||||
public string LocalDataFile(Mod mod)
|
||||
=> mod.IsTemporary ? string.Empty : LocalDataFile(mod.ModPath.FullName);
|
||||
=> LocalDataFile(mod.ModPath.FullName);
|
||||
|
||||
/// <summary> Obtain the path of the local data file given a mod directory. </summary>
|
||||
public string LocalDataFile(string modDirectory)
|
||||
|
|
@ -66,7 +66,7 @@ public class FilenameService
|
|||
|
||||
/// <summary> Obtain the path of the meta file for a given mod. Returns an empty string if the mod is temporary. </summary>
|
||||
public string ModMetaPath(Mod mod)
|
||||
=> mod.IsTemporary ? string.Empty : ModMetaPath(mod.ModPath.FullName);
|
||||
=> ModMetaPath(mod.ModPath.FullName);
|
||||
|
||||
/// <summary> Obtain the path of the meta file given a mod directory. </summary>
|
||||
public string ModMetaPath(string modDirectory)
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
private void CreateMod()
|
||||
{
|
||||
var newDir = Mod.Creator.CreateModFolder(_modManager.BasePath, _newModName);
|
||||
Mod.Creator.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
||||
_modManager.DataEditor.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
||||
Mod.Creator.CreateDefaultFiles(newDir);
|
||||
_modManager.AddMod(newDir);
|
||||
if (!_swapData.WriteMod(_modManager.Last(),
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
_itemSwapTab.DrawContent();
|
||||
}
|
||||
|
||||
// A row of three buttonSizes and a help marker that can be used for material suffix changing.
|
||||
/// <summary> A row of three buttonSizes and a help marker that can be used for material suffix changing. </summary>
|
||||
private static class MaterialSuffix
|
||||
{
|
||||
private static string _materialSuffixFrom = string.Empty;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ using OtterGui.Raii;
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Import;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.UI.Classes;
|
||||
|
|
@ -78,7 +78,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
_communicator.CollectionChange.Event += OnCollectionChange;
|
||||
_collectionManager.Current.ModSettingChanged += OnSettingChange;
|
||||
_collectionManager.Current.InheritanceChanged += OnInheritanceChange;
|
||||
_modManager.ModDataChanged += OnModDataChange;
|
||||
_communicator.ModDataChanged.Event += OnModDataChange;
|
||||
_modManager.ModDiscoveryStarted += StoreCurrentSelection;
|
||||
_modManager.ModDiscoveryFinished += RestoreLastSelection;
|
||||
OnCollectionChange(CollectionType.Current, null, _collectionManager.Current, "");
|
||||
|
|
@ -89,7 +89,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
base.Dispose();
|
||||
_modManager.ModDiscoveryStarted -= StoreCurrentSelection;
|
||||
_modManager.ModDiscoveryFinished -= RestoreLastSelection;
|
||||
_modManager.ModDataChanged -= OnModDataChange;
|
||||
_communicator.ModDataChanged.Event -= OnModDataChange;
|
||||
_collectionManager.Current.ModSettingChanged -= OnSettingChange;
|
||||
_collectionManager.Current.InheritanceChanged -= OnInheritanceChange;
|
||||
_communicator.CollectionChange.Event -= OnCollectionChange;
|
||||
|
|
@ -127,7 +127,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
try
|
||||
{
|
||||
var newDir = Mod.Creator.CreateModFolder(Penumbra.ModManager.BasePath, _newModName);
|
||||
Mod.Creator.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty);
|
||||
_modManager.DataEditor.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty);
|
||||
Mod.Creator.CreateDefaultFiles(newDir);
|
||||
Penumbra.ModManager.AddMod(newDir);
|
||||
_newModName = string.Empty;
|
||||
|
|
@ -187,29 +187,29 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
private void ToggleLeafFavorite(FileSystem<Mod>.Leaf mod)
|
||||
{
|
||||
if (ImGui.MenuItem(mod.Value.Favorite ? "Remove Favorite" : "Mark as Favorite"))
|
||||
_modManager.ChangeModFavorite(mod.Value.Index, !mod.Value.Favorite);
|
||||
_modManager.DataEditor.ChangeModFavorite(mod.Value, !mod.Value.Favorite);
|
||||
}
|
||||
|
||||
private void SetDefaultImportFolder(ModFileSystem.Folder folder)
|
||||
{
|
||||
if (ImGui.MenuItem("Set As Default Import Folder"))
|
||||
{
|
||||
var newName = folder.FullName();
|
||||
if (newName != _config.DefaultImportFolder)
|
||||
{
|
||||
_config.DefaultImportFolder = newName;
|
||||
_config.Save();
|
||||
}
|
||||
}
|
||||
if (!ImGui.MenuItem("Set As Default Import Folder"))
|
||||
return;
|
||||
|
||||
var newName = folder.FullName();
|
||||
if (newName == _config.DefaultImportFolder)
|
||||
return;
|
||||
|
||||
_config.DefaultImportFolder = newName;
|
||||
_config.Save();
|
||||
}
|
||||
|
||||
private void ClearDefaultImportFolder()
|
||||
{
|
||||
if (ImGui.MenuItem("Clear Default Import Folder") && _config.DefaultImportFolder.Length > 0)
|
||||
{
|
||||
_config.DefaultImportFolder = string.Empty;
|
||||
_config.Save();
|
||||
}
|
||||
if (!ImGui.MenuItem("Clear Default Import Folder") || _config.DefaultImportFolder.Length <= 0)
|
||||
return;
|
||||
|
||||
_config.DefaultImportFolder = string.Empty;
|
||||
_config.Save();
|
||||
}
|
||||
|
||||
private string _newModName = string.Empty;
|
||||
|
|
@ -241,7 +241,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
|
|||
return;
|
||||
|
||||
_import = new TexToolsImporter(_modManager.BasePath, f.Count, f.Select(file => new FileInfo(file)),
|
||||
AddNewMod, _config, _modEditor);
|
||||
AddNewMod, _config, _modEditor, _modManager);
|
||||
ImGui.OpenPopup("Import Status");
|
||||
}, 0, modPath, _config.AlwaysOpenDefaultImport);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class ModPanelDescriptionTab : ITab
|
|||
out var editedTag);
|
||||
_tutorial.OpenTutorial(BasicTutorialSteps.Tags);
|
||||
if (tagIdx >= 0)
|
||||
_modManager.ChangeLocalTag(_selector.Selected!.Index, tagIdx, editedTag);
|
||||
_modManager.DataEditor.ChangeLocalTag(_selector.Selected!, tagIdx, editedTag);
|
||||
|
||||
if (_selector.Selected!.ModTags.Count > 0)
|
||||
_modTags.Draw("Mod Tags: ", "Tags assigned by the mod creator and saved with the mod data. To edit these, look at Edit Mod.",
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ public class ModPanelEditTab : ITab
|
|||
var tagIdx = _modTags.Draw("Mod Tags: ", "Edit tags by clicking them, or add new tags. Empty tags are removed.", _mod.ModTags,
|
||||
out var editedTag);
|
||||
if (tagIdx >= 0)
|
||||
_modManager.ChangeModTag(_mod.Index, tagIdx, editedTag);
|
||||
_modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag);
|
||||
|
||||
UiHelpers.DefaultLineSpace();
|
||||
AddOptionGroup.Draw(_modManager, _mod);
|
||||
|
|
@ -172,18 +172,18 @@ public class ModPanelEditTab : ITab
|
|||
private void EditRegularMeta()
|
||||
{
|
||||
if (Input.Text("Name", Input.Name, Input.None, _mod.Name, out var newName, 256, UiHelpers.InputTextWidth.X))
|
||||
_modManager.ChangeModName(_mod.Index, newName);
|
||||
_modManager.DataEditor.ChangeModName(_mod, newName);
|
||||
|
||||
if (Input.Text("Author", Input.Author, Input.None, _mod.Author, out var newAuthor, 256, UiHelpers.InputTextWidth.X))
|
||||
Penumbra.ModManager.ChangeModAuthor(_mod.Index, newAuthor);
|
||||
_modManager.DataEditor.ChangeModAuthor(_mod, newAuthor);
|
||||
|
||||
if (Input.Text("Version", Input.Version, Input.None, _mod.Version, out var newVersion, 32,
|
||||
UiHelpers.InputTextWidth.X))
|
||||
_modManager.ChangeModVersion(_mod.Index, newVersion);
|
||||
_modManager.DataEditor.ChangeModVersion(_mod, newVersion);
|
||||
|
||||
if (Input.Text("Website", Input.Website, Input.None, _mod.Website, out var newWebsite, 256,
|
||||
UiHelpers.InputTextWidth.X))
|
||||
_modManager.ChangeModWebsite(_mod.Index, newWebsite);
|
||||
_modManager.DataEditor.ChangeModWebsite(_mod, newWebsite);
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(UiHelpers.ScaleX3));
|
||||
|
||||
|
|
@ -192,13 +192,13 @@ public class ModPanelEditTab : ITab
|
|||
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_mod, Input.Description));
|
||||
|
||||
ImGui.SameLine();
|
||||
var fileExists = File.Exists(_mod.MetaFile.FullName);
|
||||
var fileExists = File.Exists(_modManager.DataEditor.MetaFile(_mod));
|
||||
var tt = fileExists
|
||||
? "Open the metadata json file in the text editor of your choice."
|
||||
: "The metadata json file does not exist.";
|
||||
if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.FileExport.ToIconString()}##metaFile", UiHelpers.IconButtonSize, tt,
|
||||
!fileExists, true))
|
||||
Process.Start(new ProcessStartInfo(_mod.MetaFile.FullName) { UseShellExecute = true });
|
||||
Process.Start(new ProcessStartInfo(_modManager.DataEditor.MetaFile(_mod)) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
/// <summary> Do some edits outside of iterations. </summary>
|
||||
|
|
@ -349,7 +349,7 @@ public class ModPanelEditTab : ITab
|
|||
switch (_newDescriptionIdx)
|
||||
{
|
||||
case Input.Description:
|
||||
modManager.ChangeModDescription(_mod.Index, _newDescription);
|
||||
modManager.DataEditor.ChangeModDescription(_mod, _newDescription);
|
||||
break;
|
||||
case >= 0:
|
||||
if (_newDescriptionOptionIdx < 0)
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ public class ModPanelTabBar
|
|||
|
||||
ImGui.SetCursorPos(newPos);
|
||||
if (ImGui.Button(FontAwesomeIcon.Star.ToIconString()))
|
||||
_modManager.ChangeModFavorite(mod.Index, !mod.Favorite);
|
||||
_modManager.DataEditor.ChangeModFavorite(mod, !mod.Favorite);
|
||||
}
|
||||
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Util;
|
||||
|
|
@ -12,7 +10,7 @@ namespace Penumbra.Util;
|
|||
/// <summary>
|
||||
/// Any file type that we want to save via SaveService.
|
||||
/// </summary>
|
||||
public interface ISaveable
|
||||
public interface ISavable
|
||||
{
|
||||
/// <summary> The full file name of a given object. </summary>
|
||||
public string ToFilename(FilenameService fileNames);
|
||||
|
|
@ -42,7 +40,7 @@ public class SaveService
|
|||
}
|
||||
|
||||
/// <summary> Queue a save for the next framework tick. </summary>
|
||||
public void QueueSave(ISaveable value)
|
||||
public void QueueSave(ISavable value)
|
||||
{
|
||||
var file = value.ToFilename(_fileNames);
|
||||
_framework.RegisterDelayed(value.GetType().Name + file, () =>
|
||||
|
|
@ -52,7 +50,7 @@ public class SaveService
|
|||
}
|
||||
|
||||
/// <summary> Immediately trigger a save. </summary>
|
||||
public void ImmediateSave(ISaveable value)
|
||||
public void ImmediateSave(ISavable value)
|
||||
{
|
||||
var name = value.ToFilename(_fileNames);
|
||||
try
|
||||
|
|
@ -75,7 +73,7 @@ public class SaveService
|
|||
}
|
||||
}
|
||||
|
||||
public void ImmediateDelete(ISaveable value)
|
||||
public void ImmediateDelete(ISavable value)
|
||||
{
|
||||
var name = value.ToFilename(_fileNames);
|
||||
try
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue