Add option to auto-deduplicate on import.

This commit is contained in:
Ottermandias 2022-06-11 22:12:54 +02:00
parent d2eae54149
commit 02f1a4cedd
6 changed files with 98 additions and 11 deletions

View file

@ -48,6 +48,7 @@ public partial class Configuration : IPluginConfiguration
public bool FixMainWindow { get; set; } = false; public bool FixMainWindow { get; set; } = false;
public bool ShowAdvanced { get; set; } public bool ShowAdvanced { get; set; }
public bool AutoDeduplicateOnImport { get; set; } = false;
public bool DisableSoundStreaming { get; set; } = true; public bool DisableSoundStreaming { get; set; } = true;
public bool EnableHttpApi { get; set; } public bool EnableHttpApi { get; set; }

View file

@ -5,5 +5,6 @@ public enum ImporterState
None, None,
WritingPackToDisk, WritingPackToDisk,
ExtractingModFiles, ExtractingModFiles,
DeduplicatingFiles,
Done, Done,
} }

View file

@ -1,14 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Logging; using Dalamud.Logging;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json; using Newtonsoft.Json;
using Penumbra.Util; using Penumbra.Mods;
using FileMode = System.IO.FileMode; using FileMode = System.IO.FileMode;
namespace Penumbra.Import; namespace Penumbra.Import;
@ -95,6 +94,11 @@ public partial class TexToolsImporter : IDisposable
{ {
var directory = VerifyVersionAndImport( file ); var directory = VerifyVersionAndImport( file );
ExtractedMods.Add( ( file, directory, null ) ); ExtractedMods.Add( ( file, directory, null ) );
if( Penumbra.Config.AutoDeduplicateOnImport )
{
State = ImporterState.DeduplicatingFiles;
Mod.Editor.DeduplicateMod( directory );
}
} }
catch( Exception e ) catch( Exception e )
{ {

View file

@ -38,7 +38,14 @@ public partial class TexToolsImporter
var percentage = _modPackCount / ( float )_currentModPackIdx; var percentage = _modPackCount / ( float )_currentModPackIdx;
ImGui.ProgressBar( percentage, size, $"Mod {_currentModPackIdx + 1} / {_modPackCount}" ); ImGui.ProgressBar( percentage, size, $"Mod {_currentModPackIdx + 1} / {_modPackCount}" );
ImGui.NewLine(); ImGui.NewLine();
ImGui.TextUnformatted( $"Extracting {_currentModName}..." ); if( State == ImporterState.DeduplicatingFiles )
{
ImGui.TextUnformatted( $"Deduplicating {_currentModName}..." );
}
else
{
ImGui.TextUnformatted( $"Extracting {_currentModName}..." );
}
if( _currentNumOptions > 1 ) if( _currentNumOptions > 1 )
{ {
@ -47,8 +54,11 @@ public partial class TexToolsImporter
percentage = _currentNumOptions == 0 ? 1f : _currentOptionIdx / ( float )_currentNumOptions; percentage = _currentNumOptions == 0 ? 1f : _currentOptionIdx / ( float )_currentNumOptions;
ImGui.ProgressBar( percentage, size, $"Option {_currentOptionIdx + 1} / {_currentNumOptions}" ); ImGui.ProgressBar( percentage, size, $"Option {_currentOptionIdx + 1} / {_currentNumOptions}" );
ImGui.NewLine(); ImGui.NewLine();
ImGui.TextUnformatted( if( State != ImporterState.DeduplicatingFiles )
$"Extracting option {( _currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - " )}{_currentOptionName}..." ); {
ImGui.TextUnformatted(
$"Extracting option {( _currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - " )}{_currentOptionName}..." );
}
} }
ImGui.NewLine(); ImGui.NewLine();
@ -56,7 +66,10 @@ public partial class TexToolsImporter
percentage = _currentNumFiles == 0 ? 1f : _currentFileIdx / ( float )_currentNumFiles; percentage = _currentNumFiles == 0 ? 1f : _currentFileIdx / ( float )_currentNumFiles;
ImGui.ProgressBar( percentage, size, $"File {_currentFileIdx + 1} / {_currentNumFiles}" ); ImGui.ProgressBar( percentage, size, $"File {_currentFileIdx + 1} / {_currentNumFiles}" );
ImGui.NewLine(); ImGui.NewLine();
ImGui.TextUnformatted( $"Extracting file {_currentFileName}..." ); if( State != ImporterState.DeduplicatingFiles )
{
ImGui.TextUnformatted( $"Extracting file {_currentFileName}..." );
}
} }
} }

View file

@ -24,7 +24,7 @@ public partial class Mod
public bool DuplicatesFinished { get; private set; } = true; public bool DuplicatesFinished { get; private set; } = true;
public void DeleteDuplicates() public void DeleteDuplicates( bool useModManager = true )
{ {
if( !DuplicatesFinished || _duplicates.Count == 0 ) if( !DuplicatesFinished || _duplicates.Count == 0 )
{ {
@ -41,15 +41,16 @@ public partial class Mod
var remaining = set[ 0 ]; var remaining = set[ 0 ];
foreach( var duplicate in set.Skip( 1 ) ) foreach( var duplicate in set.Skip( 1 ) )
{ {
HandleDuplicate( duplicate, remaining ); HandleDuplicate( duplicate, remaining, useModManager );
} }
} }
_availableFiles.RemoveAll( p => !p.File.Exists ); _availableFiles.RemoveAll( p => !p.File.Exists );
_duplicates.Clear(); _duplicates.Clear();
DeleteEmptyDirectories( _mod.ModPath );
} }
private void HandleDuplicate( FullPath duplicate, FullPath remaining ) private void HandleDuplicate( FullPath duplicate, FullPath remaining, bool useModManager )
{ {
void HandleSubMod( ISubMod subMod, int groupIdx, int optionIdx ) void HandleSubMod( ISubMod subMod, int groupIdx, int optionIdx )
{ {
@ -58,7 +59,23 @@ public partial class Mod
kvp => ChangeDuplicatePath( kvp.Value, duplicate, remaining, kvp.Key, ref changes ) ); kvp => ChangeDuplicatePath( kvp.Value, duplicate, remaining, kvp.Key, ref changes ) );
if( changes ) if( changes )
{ {
Penumbra.ModManager.OptionSetFiles( _mod, groupIdx, optionIdx, dict ); if( useModManager )
{
Penumbra.ModManager.OptionSetFiles( _mod, groupIdx, optionIdx, dict );
}
else
{
var sub = ( SubMod )subMod;
sub.FileData = dict;
if( groupIdx == -1 )
{
_mod.SaveDefaultMod();
}
else
{
IModGroup.Save( _mod.Groups[ groupIdx ], _mod.ModPath, groupIdx );
}
}
} }
} }
@ -94,7 +111,7 @@ public partial class Mod
{ {
DuplicatesFinished = false; DuplicatesFinished = false;
UpdateFiles(); UpdateFiles();
var files = _availableFiles.OrderByDescending(f => f.FileSize).ToArray(); var files = _availableFiles.OrderByDescending( f => f.FileSize ).ToArray();
Task.Run( () => CheckDuplicates( files ) ); Task.Run( () => CheckDuplicates( files ) );
} }
} }
@ -215,5 +232,53 @@ public partial class Mod
using var stream = File.OpenRead( f.FullName ); using var stream = File.OpenRead( f.FullName );
return _hasher.ComputeHash( stream ); return _hasher.ComputeHash( stream );
} }
// Recursively delete all empty directories starting from the given directory.
// Deletes inner directories first, so that a tree of empty directories is actually deleted.
private void DeleteEmptyDirectories( DirectoryInfo baseDir )
{
try
{
if( !baseDir.Exists )
{
return;
}
foreach( var dir in baseDir.EnumerateDirectories( "*", SearchOption.TopDirectoryOnly ) )
{
DeleteEmptyDirectories( dir );
}
baseDir.Refresh();
if( !baseDir.EnumerateFileSystemInfos().Any() )
{
Directory.Delete( baseDir.FullName, false );
}
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete empty directories in {baseDir.FullName}:\n{e}" );
}
}
// Deduplicate a mod simply by its directory without any confirmation or waiting time.
internal static void DeduplicateMod( DirectoryInfo modDirectory )
{
try
{
var mod = new Mod( modDirectory );
mod.Reload( out _ );
var editor = new Editor( mod, 0, 0 );
editor.DuplicatesFinished = false;
editor.CheckDuplicates( editor.AvailableFiles.OrderByDescending( f => f.FileSize ).ToArray() );
editor.DeleteDuplicates( false );
}
catch( Exception e )
{
PluginLog.Warning( $"Could not deduplicate mod {modDirectory.Name}:\n{e}" );
}
}
} }
} }

View file

@ -20,6 +20,9 @@ public partial class ConfigWindow
return; return;
} }
Checkbox( "Auto Deduplicate on Import",
"Automatically deduplicate mod files on import. This will make mod file sizes smaller, but deletes (binary identical) files.",
Penumbra.Config.AutoDeduplicateOnImport, v => Penumbra.Config.AutoDeduplicateOnImport = v );
DrawRequestedResourceLogging(); DrawRequestedResourceLogging();
DrawDisableSoundStreamingBox(); DrawDisableSoundStreamingBox();
DrawEnableHttpApiBox(); DrawEnableHttpApiBox();