Rename Mod BasePath to ModPath, add simple Directory Renaming and Reloading, some fixes, Cleanup EditWindow.

This commit is contained in:
Ottermandias 2022-05-02 16:19:24 +02:00
parent c416d044a4
commit 65bbece9cf
17 changed files with 636 additions and 368 deletions

View file

@ -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();
}

View file

@ -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 )

View file

@ -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 );
}
}

View file

@ -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;
}
}

View file

@ -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 )

View file

@ -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 );
}
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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();

View file

@ -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 )