mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-30 20:33:43 +01:00
Rename Mod BasePath to ModPath, add simple Directory Renaming and Reloading, some fixes, Cleanup EditWindow.
This commit is contained in:
parent
c416d044a4
commit
65bbece9cf
17 changed files with 636 additions and 368 deletions
|
|
@ -41,7 +41,7 @@ public partial class Mod
|
|||
var dict = new Dictionary< Utf8GamePath, FullPath >( UnusedFiles.Count );
|
||||
foreach( var file in UnusedFiles )
|
||||
{
|
||||
var gamePath = file.ToGamePath( _mod.BasePath, out var g ) ? g : Utf8GamePath.Empty;
|
||||
var gamePath = file.ToGamePath( _mod.ModPath, out var g ) ? g : Utf8GamePath.Empty;
|
||||
if( !gamePath.IsEmpty && !dict.ContainsKey( gamePath ) )
|
||||
{
|
||||
dict.Add( gamePath, file );
|
||||
|
|
@ -105,7 +105,7 @@ public partial class Mod
|
|||
|
||||
|
||||
private static List<(FullPath, long)> GetAvailablePaths( Mod mod )
|
||||
=> mod.BasePath.EnumerateDirectories()
|
||||
=> mod.ModPath.EnumerateDirectories()
|
||||
.SelectMany( d => d.EnumerateFiles( "*.*", SearchOption.AllDirectories ).Select( f => (new FullPath( f ), f.Length) ) )
|
||||
.OrderBy( p => -p.Length ).ToList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,86 @@ public partial class Mod
|
|||
|
||||
// Rename/Move a mod directory.
|
||||
// Updates all collection settings and sort order settings.
|
||||
public void MoveModDirectory( Index idx, DirectoryInfo newDirectory )
|
||||
public void MoveModDirectory( int idx, string newName )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
// TODO
|
||||
var mod = this[ idx ];
|
||||
var oldName = mod.Name;
|
||||
var oldDirectory = mod.ModPath;
|
||||
|
||||
switch( NewDirectoryValid( oldDirectory.Name, newName, out var dir ) )
|
||||
{
|
||||
case NewDirectoryState.NonExisting:
|
||||
// Nothing to do
|
||||
break;
|
||||
case NewDirectoryState.ExistsEmpty:
|
||||
try
|
||||
{
|
||||
Directory.Delete( dir!.FullName );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not delete empty directory {dir!.FullName} to move {mod.Name} to it:\n{e}" );
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
// Should be caught beforehand.
|
||||
case NewDirectoryState.ExistsNonEmpty:
|
||||
case NewDirectoryState.ExistsAsFile:
|
||||
case NewDirectoryState.ContainsInvalidSymbols:
|
||||
// Nothing to do at all.
|
||||
case NewDirectoryState.Identical:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Move( oldDirectory.FullName, dir!.FullName );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not move {mod.Name} from {oldDirectory.Name} to {dir!.Name}:\n{e}" );
|
||||
return;
|
||||
}
|
||||
|
||||
dir.Refresh();
|
||||
mod.ModPath = dir;
|
||||
if( !mod.Reload( out var metaChange ) )
|
||||
{
|
||||
PluginLog.Error( $"Error reloading moved mod {mod.Name}." );
|
||||
return;
|
||||
}
|
||||
|
||||
ModPathChanged.Invoke( ModPathChangeType.Moved, mod, oldDirectory, BasePath );
|
||||
if( metaChange != MetaChangeType.None )
|
||||
{
|
||||
ModMetaChanged?.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 )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
var oldName = mod.Name;
|
||||
|
||||
if( !mod.Reload( out var metaChange ) )
|
||||
{
|
||||
PluginLog.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." );
|
||||
|
||||
DeleteMod( idx );
|
||||
return;
|
||||
}
|
||||
|
||||
ModPathChanged.Invoke( ModPathChangeType.Reloaded, mod, mod.ModPath, mod.ModPath );
|
||||
if( metaChange != MetaChangeType.None )
|
||||
{
|
||||
ModMetaChanged?.Invoke( metaChange, mod, oldName );
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a mod by its index.
|
||||
|
|
@ -28,16 +104,16 @@ public partial class Mod
|
|||
public void DeleteMod( int idx )
|
||||
{
|
||||
var mod = this[ idx ];
|
||||
if( Directory.Exists( mod.BasePath.FullName ) )
|
||||
if( Directory.Exists( mod.ModPath.FullName ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete( mod.BasePath.FullName, true );
|
||||
PluginLog.Debug( "Deleted directory {Directory:l} for {Name:l}.", mod.BasePath.FullName, mod.Name );
|
||||
Directory.Delete( mod.ModPath.FullName, true );
|
||||
PluginLog.Debug( "Deleted directory {Directory:l} for {Name:l}.", mod.ModPath.FullName, mod.Name );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not delete the mod {mod.BasePath.Name}:\n{e}" );
|
||||
PluginLog.Error( $"Could not delete the mod {mod.ModPath.Name}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,14 +123,14 @@ public partial class Mod
|
|||
--remainingMod.Index;
|
||||
}
|
||||
|
||||
ModPathChanged.Invoke( ModPathChangeType.Deleted, mod, mod.BasePath, null );
|
||||
ModPathChanged.Invoke( ModPathChangeType.Deleted, mod, mod.ModPath, null );
|
||||
PluginLog.Debug( "Deleted mod {Name:l}.", mod.Name );
|
||||
}
|
||||
|
||||
// Load a new mod and add it to the manager if successful.
|
||||
public void AddMod( DirectoryInfo modFolder )
|
||||
{
|
||||
if( _mods.Any( m => m.BasePath.Name == modFolder.Name ) )
|
||||
if( _mods.Any( m => m.ModPath.Name == modFolder.Name ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -67,10 +143,61 @@ public partial class Mod
|
|||
|
||||
mod.Index = _mods.Count;
|
||||
_mods.Add( mod );
|
||||
ModPathChanged.Invoke( ModPathChangeType.Added, mod, null, mod.BasePath );
|
||||
PluginLog.Debug( "Added new mod {Name:l} from {Directory:l}.", mod.Name, modFolder.FullName );
|
||||
ModPathChanged.Invoke( ModPathChangeType.Added, mod, null, mod.ModPath );
|
||||
PluginLog.Debug( "Added new mod {Name:l} from {Directory:l}.", mod.Name, modFolder.FullName );
|
||||
}
|
||||
|
||||
public enum NewDirectoryState
|
||||
{
|
||||
NonExisting,
|
||||
ExistsEmpty,
|
||||
ExistsNonEmpty,
|
||||
ExistsAsFile,
|
||||
ContainsInvalidSymbols,
|
||||
Identical,
|
||||
Empty,
|
||||
}
|
||||
|
||||
// Return the state of the new potential name of a directory.
|
||||
public static NewDirectoryState NewDirectoryValid( string oldName, string newName, out DirectoryInfo? directory )
|
||||
{
|
||||
directory = null;
|
||||
if( newName.Length == 0 )
|
||||
{
|
||||
return NewDirectoryState.Empty;
|
||||
}
|
||||
|
||||
if( oldName == newName )
|
||||
{
|
||||
return NewDirectoryState.Identical;
|
||||
}
|
||||
|
||||
var fixedNewName = ReplaceBadXivSymbols( newName );
|
||||
if( fixedNewName != newName )
|
||||
{
|
||||
return NewDirectoryState.ContainsInvalidSymbols;
|
||||
}
|
||||
|
||||
directory = new DirectoryInfo( Path.Combine( Penumbra.ModManager.BasePath.FullName, fixedNewName ) );
|
||||
if( File.Exists( directory.FullName ) )
|
||||
{
|
||||
return NewDirectoryState.ExistsAsFile;
|
||||
}
|
||||
|
||||
if( !Directory.Exists( directory.FullName ) )
|
||||
{
|
||||
return NewDirectoryState.NonExisting;
|
||||
}
|
||||
|
||||
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 )
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public sealed partial class Mod
|
|||
return;
|
||||
}
|
||||
|
||||
group.DeleteFile( mod.BasePath, groupIdx );
|
||||
group.DeleteFile( mod.ModPath, groupIdx );
|
||||
|
||||
var _ = group switch
|
||||
{
|
||||
|
|
@ -86,7 +86,7 @@ public sealed partial class Mod
|
|||
{
|
||||
var group = mod._groups[ groupIdx ];
|
||||
mod._groups.RemoveAt( groupIdx );
|
||||
group.DeleteFile( mod.BasePath, groupIdx );
|
||||
group.DeleteFile( mod.ModPath, groupIdx );
|
||||
ModOptionChanged.Invoke( ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1 );
|
||||
}
|
||||
|
||||
|
|
@ -426,7 +426,7 @@ public sealed partial class Mod
|
|||
}
|
||||
else
|
||||
{
|
||||
IModGroup.SaveModGroup( mod._groups[ groupIdx ], mod.BasePath, groupIdx );
|
||||
IModGroup.SaveModGroup( mod._groups[ groupIdx ], mod.ModPath, groupIdx );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,37 +8,53 @@ public enum ModPathChangeType
|
|||
Added,
|
||||
Deleted,
|
||||
Moved,
|
||||
Reloaded,
|
||||
}
|
||||
|
||||
public partial class Mod
|
||||
{
|
||||
public DirectoryInfo BasePath { get; private set; }
|
||||
public DirectoryInfo ModPath { get; private set; }
|
||||
public int Index { get; private set; } = -1;
|
||||
|
||||
private Mod( DirectoryInfo basePath )
|
||||
=> BasePath = basePath;
|
||||
private Mod( DirectoryInfo modPath )
|
||||
=> ModPath = modPath;
|
||||
|
||||
private static Mod? LoadMod( DirectoryInfo basePath )
|
||||
private static Mod? LoadMod( DirectoryInfo modPath )
|
||||
{
|
||||
basePath.Refresh();
|
||||
if( !basePath.Exists )
|
||||
modPath.Refresh();
|
||||
if( !modPath.Exists )
|
||||
{
|
||||
PluginLog.Error( $"Supplied mod directory {basePath} does not exist." );
|
||||
PluginLog.Error( $"Supplied mod directory {modPath} does not exist." );
|
||||
return null;
|
||||
}
|
||||
|
||||
var mod = new Mod( basePath );
|
||||
mod.LoadMeta();
|
||||
if( mod.Name.Length == 0 )
|
||||
var mod = new Mod( modPath );
|
||||
if( !mod.Reload(out _) )
|
||||
{
|
||||
PluginLog.Error( $"Mod at {basePath} without name is not supported." );
|
||||
// Can not be base path not existing because that is checked before.
|
||||
PluginLog.Error( $"Mod at {modPath} without name is not supported." );
|
||||
}
|
||||
|
||||
mod.LoadDefaultOption();
|
||||
mod.LoadAllGroups();
|
||||
mod.ComputeChangedItems();
|
||||
mod.SetCounts();
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
private bool Reload(out MetaChangeType metaChange)
|
||||
{
|
||||
metaChange = MetaChangeType.Deletion;
|
||||
ModPath.Refresh();
|
||||
if( !ModPath.Exists )
|
||||
return false;
|
||||
|
||||
metaChange = LoadMeta();
|
||||
if( metaChange.HasFlag(MetaChangeType.Deletion) || Name.Length == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LoadDefaultOption();
|
||||
LoadAllGroups();
|
||||
ComputeChangedItems();
|
||||
SetCounts();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -141,7 +141,7 @@ public partial class Mod
|
|||
|
||||
// XIV can not deal with non-ascii symbols in a path,
|
||||
// and the path must obviously be valid itself.
|
||||
private static string ReplaceBadXivSymbols( string s, string replacement = "_" )
|
||||
public static string ReplaceBadXivSymbols( string s, string replacement = "_" )
|
||||
{
|
||||
StringBuilder sb = new(s.Length);
|
||||
foreach( var c in s )
|
||||
|
|
|
|||
|
|
@ -59,12 +59,12 @@ public partial class Mod
|
|||
.Select( p => p.Value );
|
||||
|
||||
public IEnumerable< FileInfo > GroupFiles
|
||||
=> BasePath.EnumerateFiles( "group_*.json" );
|
||||
=> ModPath.EnumerateFiles( "group_*.json" );
|
||||
|
||||
public List< FullPath > FindUnusedFiles()
|
||||
{
|
||||
var modFiles = AllFiles.ToHashSet();
|
||||
return BasePath.EnumerateDirectories()
|
||||
return ModPath.EnumerateDirectories()
|
||||
.SelectMany( f => f.EnumerateFiles( "*", SearchOption.AllDirectories ) )
|
||||
.Select( f => new FullPath( f ) )
|
||||
.Where( f => !modFiles.Contains( f ) )
|
||||
|
|
@ -107,7 +107,7 @@ public partial class Mod
|
|||
_groups.Clear();
|
||||
foreach( var file in GroupFiles )
|
||||
{
|
||||
var group = LoadModGroup( file, BasePath );
|
||||
var group = LoadModGroup( file, ModPath );
|
||||
if( group != null )
|
||||
{
|
||||
_groups.Add( group );
|
||||
|
|
@ -136,7 +136,7 @@ public partial class Mod
|
|||
|
||||
foreach( var (group, index) in _groups.WithIndex() )
|
||||
{
|
||||
IModGroup.SaveModGroup( group, BasePath, index );
|
||||
IModGroup.SaveModGroup( group, ModPath, index );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ public sealed partial class Mod
|
|||
|
||||
foreach( var unusedFile in mod.FindUnusedFiles().Where( f => !seenMetaFiles.Contains( f ) ) )
|
||||
{
|
||||
if( unusedFile.ToGamePath( mod.BasePath, out var gamePath )
|
||||
if( unusedFile.ToGamePath( mod.ModPath, out var gamePath )
|
||||
&& !mod._default.FileData.TryAdd( gamePath, unusedFile ) )
|
||||
{
|
||||
PluginLog.Error( $"Could not add {gamePath} because it already points to {mod._default.FileData[ gamePath ]}." );
|
||||
|
|
@ -80,10 +80,10 @@ public sealed partial class Mod
|
|||
mod._default.FileSwapData.Add( gamePath, swapPath );
|
||||
}
|
||||
|
||||
mod._default.IncorporateMetaChanges( mod.BasePath, true );
|
||||
mod._default.IncorporateMetaChanges( mod.ModPath, true );
|
||||
foreach( var (group, index) in mod.Groups.WithIndex() )
|
||||
{
|
||||
IModGroup.SaveModGroup( group, mod.BasePath, index );
|
||||
IModGroup.SaveModGroup( group, mod.ModPath, index );
|
||||
}
|
||||
|
||||
// Delete meta files.
|
||||
|
|
@ -100,7 +100,7 @@ public sealed partial class Mod
|
|||
}
|
||||
|
||||
// Delete old meta files.
|
||||
var oldMetaFile = Path.Combine( mod.BasePath.FullName, "metadata_manipulations.json" );
|
||||
var oldMetaFile = Path.Combine( mod.ModPath.FullName, "metadata_manipulations.json" );
|
||||
if( File.Exists( oldMetaFile ) )
|
||||
{
|
||||
try
|
||||
|
|
@ -141,14 +141,14 @@ public sealed partial class Mod
|
|||
mod._groups.Add( newMultiGroup );
|
||||
foreach( var option in group.Options )
|
||||
{
|
||||
newMultiGroup.PrioritizedOptions.Add( ( SubModFromOption( mod.BasePath, option, seenMetaFiles ), optionPriority++ ) );
|
||||
newMultiGroup.PrioritizedOptions.Add( ( SubModFromOption( mod.ModPath, option, seenMetaFiles ), optionPriority++ ) );
|
||||
}
|
||||
|
||||
break;
|
||||
case SelectType.Single:
|
||||
if( group.Options.Count == 1 )
|
||||
{
|
||||
AddFilesToSubMod( mod._default, mod.BasePath, group.Options[ 0 ], seenMetaFiles );
|
||||
AddFilesToSubMod( mod._default, mod.ModPath, group.Options[ 0 ], seenMetaFiles );
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ public sealed partial class Mod
|
|||
mod._groups.Add( newSingleGroup );
|
||||
foreach( var option in group.Options )
|
||||
{
|
||||
newSingleGroup.OptionData.Add( SubModFromOption( mod.BasePath, option, seenMetaFiles ) );
|
||||
newSingleGroup.OptionData.Add( SubModFromOption( mod.ModPath, option, seenMetaFiles ) );
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -34,14 +34,14 @@ public sealed partial class Mod
|
|||
public long ImportDate { get; private set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
internal FileInfo MetaFile
|
||||
=> new(Path.Combine( BasePath.FullName, "meta.json" ));
|
||||
=> new(Path.Combine( ModPath.FullName, "meta.json" ));
|
||||
|
||||
private MetaChangeType LoadMeta()
|
||||
{
|
||||
var metaFile = MetaFile;
|
||||
if( !File.Exists( metaFile.FullName ) )
|
||||
{
|
||||
PluginLog.Debug( "No mod meta found for {ModLocation}.", BasePath.Name );
|
||||
PluginLog.Debug( "No mod meta found for {ModLocation}.", ModPath.Name );
|
||||
return MetaChangeType.Deletion;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,12 +102,15 @@ public sealed class ModFileSystem : FileSystem< Mod >, IDisposable
|
|||
case ModPathChangeType.Moved:
|
||||
Save();
|
||||
break;
|
||||
case ModPathChangeType.Reloaded:
|
||||
// Nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Used for saving and loading.
|
||||
private static string ModToIdentifier( Mod mod )
|
||||
=> mod.BasePath.Name;
|
||||
=> mod.ModPath.Name;
|
||||
|
||||
private static string ModToName( Mod mod )
|
||||
=> mod.Name.Text.FixName();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace Penumbra.Mods;
|
|||
public partial class Mod
|
||||
{
|
||||
internal string DefaultFile
|
||||
=> Path.Combine( BasePath.FullName, "default_mod.json" );
|
||||
=> Path.Combine( ModPath.FullName, "default_mod.json" );
|
||||
|
||||
// The default mod contains setting-independent sets of file replacements, file swaps and meta changes.
|
||||
// Every mod has an default mod, though it may be empty.
|
||||
|
|
@ -33,7 +33,7 @@ public partial class Mod
|
|||
{
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
ISubMod.WriteSubMod( j, serializer, _default, BasePath, 0 );
|
||||
ISubMod.WriteSubMod( j, serializer, _default, ModPath, 0 );
|
||||
}
|
||||
|
||||
private void LoadDefaultOption()
|
||||
|
|
@ -43,11 +43,11 @@ public partial class Mod
|
|||
{
|
||||
if( !File.Exists( defaultFile ) )
|
||||
{
|
||||
_default.Load( BasePath, new JObject(), out _ );
|
||||
_default.Load( ModPath, new JObject(), out _ );
|
||||
}
|
||||
else
|
||||
{
|
||||
_default.Load( BasePath, JObject.Parse( File.ReadAllText( defaultFile ) ), out _ );
|
||||
_default.Load( ModPath, JObject.Parse( File.ReadAllText( defaultFile ) ), out _ );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue