mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
158 lines
5.9 KiB
C#
158 lines
5.9 KiB
C#
using Dalamud.Interface.ImGuiNotification;
|
|
using OtterGui.Classes;
|
|
using OtterGui.Filesystem;
|
|
using OtterGui.Services;
|
|
using Penumbra.Communication;
|
|
using Penumbra.Services;
|
|
|
|
namespace Penumbra.Mods.Manager;
|
|
|
|
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable, IService
|
|
{
|
|
private readonly ModManager _modManager;
|
|
private readonly CommunicatorService _communicator;
|
|
private readonly SaveService _saveService;
|
|
private readonly Configuration _config;
|
|
|
|
// Create a new ModFileSystem from the currently loaded mods and the current sort order file.
|
|
public ModFileSystem(ModManager modManager, CommunicatorService communicator, SaveService saveService, Configuration config)
|
|
{
|
|
_modManager = modManager;
|
|
_communicator = communicator;
|
|
_saveService = saveService;
|
|
_config = config;
|
|
Reload();
|
|
Changed += OnChange;
|
|
_communicator.ModDiscoveryFinished.Subscribe(Reload, ModDiscoveryFinished.Priority.ModFileSystem);
|
|
_communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystem);
|
|
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModFileSystem);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_communicator.ModPathChanged.Unsubscribe(OnModPathChange);
|
|
_communicator.ModDiscoveryFinished.Unsubscribe(Reload);
|
|
_communicator.ModDataChanged.Unsubscribe(OnModDataChange);
|
|
}
|
|
|
|
public struct ImportDate : ISortMode<Mod>
|
|
{
|
|
public string Name
|
|
=> "Import Date (Older First)";
|
|
|
|
public string Description
|
|
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their import date.";
|
|
|
|
public IEnumerable<IPath> GetChildren(Folder f)
|
|
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.ImportDate));
|
|
}
|
|
|
|
public struct InverseImportDate : ISortMode<Mod>
|
|
{
|
|
public string Name
|
|
=> "Import Date (Newer First)";
|
|
|
|
public string Description
|
|
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse import date.";
|
|
|
|
public IEnumerable<IPath> GetChildren(Folder f)
|
|
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.ImportDate));
|
|
}
|
|
|
|
// Reload the whole filesystem from currently loaded mods and the current sort order file.
|
|
// Used on construction and on mod rediscoveries.
|
|
private void Reload()
|
|
{
|
|
var jObj = BackupService.GetJObjectForFile(_saveService.FileNames, _saveService.FileNames.FilesystemFile);
|
|
if (Load(jObj, _modManager, ModToIdentifier, ModToName))
|
|
_saveService.ImmediateSave(this);
|
|
|
|
Penumbra.Log.Debug("Reloaded mod filesystem.");
|
|
}
|
|
|
|
// Save the filesystem on every filesystem change except full reloading.
|
|
private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3)
|
|
{
|
|
if (type != FileSystemChangeType.Reload)
|
|
_saveService.DelaySave(this);
|
|
}
|
|
|
|
// Update sort order when defaulted mod names change.
|
|
private void OnModDataChange(ModDataChangeType type, Mod mod, string? oldName)
|
|
{
|
|
if (!type.HasFlag(ModDataChangeType.Name) || oldName == null || !TryGetValue(mod, out var leaf))
|
|
return;
|
|
|
|
var old = oldName.FixName();
|
|
if (old == leaf.Name || leaf.Name.IsDuplicateName(out var baseName, out _) && baseName == old)
|
|
RenameWithDuplicates(leaf, mod.Name.Text);
|
|
}
|
|
|
|
// Update the filesystem if a mod has been added or removed.
|
|
// Save it, if the mod directory has been moved, since this will change the save format.
|
|
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldPath, DirectoryInfo? newPath)
|
|
{
|
|
switch (type)
|
|
{
|
|
case ModPathChangeType.Added:
|
|
var parent = Root;
|
|
if (_config.DefaultImportFolder.Length != 0)
|
|
try
|
|
{
|
|
parent = FindOrCreateAllFolders(_config.DefaultImportFolder);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Penumbra.Messager.NotificationMessage(e,
|
|
$"Could not move newly imported mod {mod.Name} to default import folder {_config.DefaultImportFolder}.",
|
|
NotificationType.Warning);
|
|
}
|
|
|
|
CreateDuplicateLeaf(parent, mod.Name.Text, mod);
|
|
break;
|
|
case ModPathChangeType.Deleted:
|
|
if (TryGetValue(mod, out var leaf))
|
|
Delete(leaf);
|
|
|
|
break;
|
|
case ModPathChangeType.Moved:
|
|
_saveService.DelaySave(this);
|
|
break;
|
|
case ModPathChangeType.Reloaded:
|
|
// Nothing
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Used for saving and loading.
|
|
private static string ModToIdentifier(Mod mod)
|
|
=> mod.ModPath.Name;
|
|
|
|
private static string ModToName(Mod mod)
|
|
=> mod.Name.Text.FixName();
|
|
|
|
// Return whether a mod has a custom path or is just a numbered default path.
|
|
public static bool ModHasDefaultPath(Mod mod, string fullPath)
|
|
{
|
|
var regex = new Regex($@"^{Regex.Escape(ModToName(mod))}( \(\d+\))?$");
|
|
return regex.IsMatch(fullPath);
|
|
}
|
|
|
|
private static (string, bool) SaveMod(Mod mod, string fullPath)
|
|
// Only save pairs with non-default paths.
|
|
=> ModHasDefaultPath(mod, fullPath)
|
|
? (string.Empty, false)
|
|
: (ModToIdentifier(mod), true);
|
|
|
|
public string ToFilename(FilenameService fileNames)
|
|
=> fileNames.FilesystemFile;
|
|
|
|
public void Save(StreamWriter writer)
|
|
=> SaveToFile(writer, SaveMod, true);
|
|
|
|
public string TypeName
|
|
=> "Mod File System";
|
|
|
|
public string LogName(string _)
|
|
=> "to file";
|
|
}
|