198 lines
7.8 KiB
C#
198 lines
7.8 KiB
C#
using System.Collections.Concurrent;
|
|
using Dalamud.Plugin;
|
|
using LightlessSync.Interop.Ipc.Framework;
|
|
using LightlessSync.Services;
|
|
using LightlessSync.Services.Mediator;
|
|
using Microsoft.Extensions.Logging;
|
|
using Penumbra.Api.Enums;
|
|
using Penumbra.Api.IpcSubscribers;
|
|
|
|
namespace LightlessSync.Interop.Ipc.Penumbra;
|
|
|
|
public sealed class PenumbraCollections : PenumbraBase
|
|
{
|
|
private readonly CreateTemporaryCollection _createNamedTemporaryCollection;
|
|
private readonly AssignTemporaryCollection _assignTemporaryCollection;
|
|
private readonly DeleteTemporaryCollection _removeTemporaryCollection;
|
|
private readonly AddTemporaryMod _addTemporaryMod;
|
|
private readonly RemoveTemporaryMod _removeTemporaryMod;
|
|
private readonly GetCollections _getCollections;
|
|
private readonly ConcurrentDictionary<Guid, string> _activeTemporaryCollections = new();
|
|
|
|
private int _cleanupScheduled;
|
|
|
|
public PenumbraCollections(
|
|
ILogger logger,
|
|
IDalamudPluginInterface pluginInterface,
|
|
DalamudUtilService dalamudUtil,
|
|
LightlessMediator mediator) : base(logger, pluginInterface, dalamudUtil, mediator)
|
|
{
|
|
_createNamedTemporaryCollection = new CreateTemporaryCollection(pluginInterface);
|
|
_assignTemporaryCollection = new AssignTemporaryCollection(pluginInterface);
|
|
_removeTemporaryCollection = new DeleteTemporaryCollection(pluginInterface);
|
|
_addTemporaryMod = new AddTemporaryMod(pluginInterface);
|
|
_removeTemporaryMod = new RemoveTemporaryMod(pluginInterface);
|
|
_getCollections = new GetCollections(pluginInterface);
|
|
}
|
|
|
|
public override string Name => "Penumbra.Collections";
|
|
|
|
public async Task AssignTemporaryCollectionAsync(ILogger logger, Guid collectionId, int objectIndex)
|
|
{
|
|
if (!IsAvailable || collectionId == Guid.Empty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
var result = _assignTemporaryCollection.Invoke(collectionId, objectIndex, forceAssignment: true);
|
|
logger.LogTrace("Assigning Temp Collection {CollectionId} to index {ObjectIndex}, Success: {Result}", collectionId, objectIndex, result);
|
|
return result;
|
|
}).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<Guid> CreateTemporaryCollectionAsync(ILogger logger, string uid)
|
|
{
|
|
if (!IsAvailable)
|
|
{
|
|
return Guid.Empty;
|
|
}
|
|
|
|
var (collectionId, collectionName) = await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
var name = $"Lightless_{uid}";
|
|
_createNamedTemporaryCollection.Invoke(name, name, out var tempCollectionId);
|
|
logger.LogTrace("Creating Temp Collection {CollectionName}, GUID: {CollectionId}", name, tempCollectionId);
|
|
return (tempCollectionId, name);
|
|
}).ConfigureAwait(false);
|
|
|
|
if (collectionId != Guid.Empty)
|
|
{
|
|
_activeTemporaryCollections[collectionId] = collectionName;
|
|
}
|
|
|
|
return collectionId;
|
|
}
|
|
|
|
public async Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collectionId)
|
|
{
|
|
if (!IsAvailable || collectionId == Guid.Empty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
logger.LogTrace("[{ApplicationId}] Removing temp collection for {CollectionId}", applicationId, collectionId);
|
|
var result = _removeTemporaryCollection.Invoke(collectionId);
|
|
logger.LogTrace("[{ApplicationId}] RemoveTemporaryCollection: {Result}", applicationId, result);
|
|
}).ConfigureAwait(false);
|
|
|
|
_activeTemporaryCollections.TryRemove(collectionId, out _);
|
|
}
|
|
|
|
public async Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collectionId, IReadOnlyDictionary<string, string> modPaths)
|
|
{
|
|
if (!IsAvailable || collectionId == Guid.Empty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
foreach (var mod in modPaths)
|
|
{
|
|
logger.LogTrace("[{ApplicationId}] Change: {From} => {To}", applicationId, mod.Key, mod.Value);
|
|
}
|
|
|
|
var removeResult = _removeTemporaryMod.Invoke("LightlessChara_Files", collectionId, 0);
|
|
logger.LogTrace("[{ApplicationId}] Removing temp files mod for {CollectionId}, Success: {Result}", applicationId, collectionId, removeResult);
|
|
|
|
var addResult = _addTemporaryMod.Invoke("LightlessChara_Files", collectionId, new Dictionary<string, string>(modPaths), string.Empty, 0);
|
|
logger.LogTrace("[{ApplicationId}] Setting temp files mod for {CollectionId}, Success: {Result}", applicationId, collectionId, addResult);
|
|
}).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task SetManipulationDataAsync(ILogger logger, Guid applicationId, Guid collectionId, string manipulationData)
|
|
{
|
|
if (!IsAvailable || collectionId == Guid.Empty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
logger.LogTrace("[{ApplicationId}] Manip: {Data}", applicationId, manipulationData);
|
|
var result = _addTemporaryMod.Invoke("LightlessChara_Meta", collectionId, [], manipulationData, 0);
|
|
logger.LogTrace("[{ApplicationId}] Setting temp meta mod for {CollectionId}, Success: {Result}", applicationId, collectionId, result);
|
|
}).ConfigureAwait(false);
|
|
}
|
|
|
|
protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
|
|
{
|
|
if (current == IpcConnectionState.Available)
|
|
{
|
|
ScheduleCleanup();
|
|
}
|
|
else if (previous == IpcConnectionState.Available && current != IpcConnectionState.Available)
|
|
{
|
|
Interlocked.Exchange(ref _cleanupScheduled, 0);
|
|
}
|
|
}
|
|
|
|
private void ScheduleCleanup()
|
|
{
|
|
if (Interlocked.Exchange(ref _cleanupScheduled, 1) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_ = Task.Run(CleanupTemporaryCollectionsAsync);
|
|
}
|
|
|
|
private async Task CleanupTemporaryCollectionsAsync()
|
|
{
|
|
if (!IsAvailable)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var collections = await DalamudUtil.RunOnFrameworkThread(() => _getCollections.Invoke()).ConfigureAwait(false);
|
|
foreach (var (collectionId, name) in collections)
|
|
{
|
|
if (!IsLightlessCollectionName(name) || _activeTemporaryCollections.ContainsKey(collectionId))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Logger.LogDebug("Cleaning up stale temporary collection {CollectionName} ({CollectionId})", name, collectionId);
|
|
var deleteResult = await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
var result = (PenumbraApiEc)_removeTemporaryCollection.Invoke(collectionId);
|
|
Logger.LogTrace("Cleanup RemoveTemporaryCollection result for {CollectionName} ({CollectionId}): {Result}", name, collectionId, result);
|
|
return result;
|
|
}).ConfigureAwait(false);
|
|
|
|
if (deleteResult == PenumbraApiEc.Success)
|
|
{
|
|
_activeTemporaryCollections.TryRemove(collectionId, out _);
|
|
}
|
|
else
|
|
{
|
|
Logger.LogDebug("Skipped removing temporary collection {CollectionName} ({CollectionId}). Result: {Result}", name, collectionId, deleteResult);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogWarning(ex, "Failed to clean up Penumbra temporary collections");
|
|
}
|
|
}
|
|
|
|
private static bool IsLightlessCollectionName(string? name)
|
|
=> !string.IsNullOrEmpty(name) && name.StartsWith("Lightless_", StringComparison.Ordinal);
|
|
}
|