1023 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			1023 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using Dalamud.Game.ClientState.Objects.Types;
 | |
| using K4os.Compression.LZ4.Legacy;
 | |
| using MareSynchronos.API.Data;
 | |
| using MareSynchronos.API.Dto.CharaData;
 | |
| using MareSynchronos.Interop.Ipc;
 | |
| using MareSynchronos.MareConfiguration;
 | |
| using MareSynchronos.PlayerData.Factories;
 | |
| using MareSynchronos.PlayerData.Handlers;
 | |
| using MareSynchronos.PlayerData.Pairs;
 | |
| using MareSynchronos.Services.CharaData.Models;
 | |
| using MareSynchronos.Services.Mediator;
 | |
| using MareSynchronos.Utils;
 | |
| using MareSynchronos.WebAPI;
 | |
| using Microsoft.Extensions.Logging;
 | |
| using System.Collections.Concurrent;
 | |
| using System.Text;
 | |
| 
 | |
| namespace MareSynchronos.Services;
 | |
| 
 | |
| public sealed partial class CharaDataManager : DisposableMediatorSubscriberBase
 | |
| {
 | |
|     private readonly ApiController _apiController;
 | |
|     private readonly CharaDataConfigService _configService;
 | |
|     private readonly DalamudUtilService _dalamudUtilService;
 | |
|     private readonly CharaDataFileHandler _fileHandler;
 | |
|     private readonly IpcManager _ipcManager;
 | |
|     private readonly ConcurrentDictionary<string, CharaDataMetaInfoExtendedDto?> _metaInfoCache = [];
 | |
|     private readonly List<CharaDataMetaInfoExtendedDto> _nearbyData = [];
 | |
|     private readonly CharaDataNearbyManager _nearbyManager;
 | |
|     private readonly CharaDataCharacterHandler _characterHandler;
 | |
|     private readonly PairManager _pairManager;
 | |
|     private readonly Dictionary<string, CharaDataFullExtendedDto> _ownCharaData = [];
 | |
|     private readonly Dictionary<string, Task> _sharedMetaInfoTimeoutTasks = [];
 | |
|     private readonly Dictionary<UserData, List<CharaDataMetaInfoExtendedDto>> _sharedWithYouData = [];
 | |
|     private readonly Dictionary<string, CharaDataExtendedUpdateDto> _updateDtos = [];
 | |
|     private CancellationTokenSource _applicationCts = new();
 | |
|     private CancellationTokenSource _charaDataCreateCts = new();
 | |
|     private CancellationTokenSource _connectCts = new();
 | |
|     private CancellationTokenSource _getAllDataCts = new();
 | |
|     private CancellationTokenSource _getSharedDataCts = new();
 | |
|     private CancellationTokenSource _uploadCts = new();
 | |
| 
 | |
|     public CharaDataManager(ILogger<CharaDataManager> logger, ApiController apiController,
 | |
|         CharaDataFileHandler charaDataFileHandler,
 | |
|         MareMediator mareMediator, IpcManager ipcManager, DalamudUtilService dalamudUtilService,
 | |
|         FileDownloadManagerFactory fileDownloadManagerFactory,
 | |
|         CharaDataConfigService charaDataConfigService, CharaDataNearbyManager charaDataNearbyManager,
 | |
|         CharaDataCharacterHandler charaDataCharacterHandler, PairManager pairManager) : base(logger, mareMediator)
 | |
|     {
 | |
|         _apiController = apiController;
 | |
|         _fileHandler = charaDataFileHandler;
 | |
|         _ipcManager = ipcManager;
 | |
|         _dalamudUtilService = dalamudUtilService;
 | |
|         _configService = charaDataConfigService;
 | |
|         _nearbyManager = charaDataNearbyManager;
 | |
|         _characterHandler = charaDataCharacterHandler;
 | |
|         _pairManager = pairManager;
 | |
|         mareMediator.Subscribe<ConnectedMessage>(this, (msg) =>
 | |
|         {
 | |
|             _connectCts?.Cancel();
 | |
|             _connectCts?.Dispose();
 | |
|             _connectCts = new();
 | |
|             _ownCharaData.Clear();
 | |
|             _metaInfoCache.Clear();
 | |
|             _sharedWithYouData.Clear();
 | |
|             _updateDtos.Clear();
 | |
|             Initialized = false;
 | |
|             MaxCreatableCharaData = msg.Connection.ServerInfo.MaxCharaData;
 | |
|             if (_configService.Current.DownloadMcdDataOnConnection)
 | |
|             {
 | |
|                 var token = _connectCts.Token;
 | |
|                 _ = GetAllData(token);
 | |
|                 _ = GetAllSharedData(token);
 | |
|             }
 | |
|         });
 | |
|         mareMediator.Subscribe<DisconnectedMessage>(this, (msg) =>
 | |
|         {
 | |
|             _ownCharaData.Clear();
 | |
|             _metaInfoCache.Clear();
 | |
|             _sharedWithYouData.Clear();
 | |
|             _updateDtos.Clear();
 | |
|             Initialized = false;
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public Task? AttachingPoseTask { get; private set; }
 | |
|     public Task? CharaUpdateTask { get; set; }
 | |
|     public string DataApplicationProgress { get; private set; } = string.Empty;
 | |
|     public Task? DataApplicationTask { get; private set; }
 | |
|     public Task<(string Output, bool Success)>? DataCreationTask { get; private set; }
 | |
|     public Task? DataGetTimeoutTask { get; private set; }
 | |
|     public Task<(string Result, bool Success)>? DownloadMetaInfoTask { get; private set; }
 | |
|     public Task<List<CharaDataFullExtendedDto>>? GetAllDataTask { get; private set; }
 | |
|     public Task<List<CharaDataMetaInfoDto>>? GetSharedWithYouTask { get; private set; }
 | |
|     public Task? GetSharedWithYouTimeoutTask { get; private set; }
 | |
|     public IReadOnlyDictionary<string, HandledCharaDataEntry> HandledCharaData => _characterHandler.HandledCharaData;
 | |
|     public bool Initialized { get; private set; }
 | |
|     public CharaDataMetaInfoExtendedDto? LastDownloadedMetaInfo { get; private set; }
 | |
|     public Task<(MareCharaFileHeader LoadedFile, long ExpectedLength)>? LoadedMcdfHeader { get; private set; }
 | |
|     public int MaxCreatableCharaData { get; private set; }
 | |
|     public Task? McdfApplicationTask { get; private set; }
 | |
|     public List<CharaDataMetaInfoExtendedDto> NearbyData => _nearbyData;
 | |
|     public IDictionary<string, CharaDataFullExtendedDto> OwnCharaData => _ownCharaData;
 | |
|     public IDictionary<UserData, List<CharaDataMetaInfoExtendedDto>> SharedWithYouData => _sharedWithYouData;
 | |
|     public Task? UiBlockingComputation { get; private set; }
 | |
|     public ValueProgress<string>? UploadProgress { get; private set; }
 | |
|     public Task<(string Output, bool Success)>? UploadTask { get; set; }
 | |
|     public bool BrioAvailable => _ipcManager.Brio.APIAvailable;
 | |
| 
 | |
|     public Task ApplyCharaData(CharaDataDownloadDto dataDownloadDto, string charaName)
 | |
|     {
 | |
|         return UiBlockingComputation = DataApplicationTask = Task.Run(async () =>
 | |
|         {
 | |
|             if (string.IsNullOrEmpty(charaName)) return;
 | |
| 
 | |
|             CharaDataMetaInfoDto metaInfo = new(dataDownloadDto.Id, dataDownloadDto.Uploader)
 | |
|             {
 | |
|                 CanBeDownloaded = true,
 | |
|                 Description = $"Data from {dataDownloadDto.Uploader.AliasOrUID} for {dataDownloadDto.Id}",
 | |
|                 UpdatedDate = dataDownloadDto.UpdatedDate,
 | |
|             };
 | |
| 
 | |
|             await DownloadAndAplyDataAsync(charaName, dataDownloadDto, metaInfo, false).ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public Task ApplyCharaData(CharaDataMetaInfoDto dataMetaInfoDto, string charaName)
 | |
|     {
 | |
|         return UiBlockingComputation = DataApplicationTask = Task.Run(async () =>
 | |
|         {
 | |
|             if (string.IsNullOrEmpty(charaName)) return;
 | |
| 
 | |
|             var download = await _apiController.CharaDataDownload(dataMetaInfoDto.Uploader.UID + ":" + dataMetaInfoDto.Id).ConfigureAwait(false);
 | |
|             if (download == null)
 | |
|             {
 | |
|                 DataApplicationTask = null;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             await DownloadAndAplyDataAsync(charaName, download, dataMetaInfoDto, false).ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public Task ApplyCharaDataToGposeTarget(CharaDataMetaInfoDto dataMetaInfoDto)
 | |
|     {
 | |
|         return UiBlockingComputation = DataApplicationTask = Task.Run(async () =>
 | |
|         {
 | |
|             var obj = await _dalamudUtilService.GetGposeTargetGameObjectAsync().ConfigureAwait(false);
 | |
|             var charaName = obj?.Name.TextValue ?? string.Empty;
 | |
|             if (string.IsNullOrEmpty(charaName)) return;
 | |
| 
 | |
|             await ApplyCharaData(dataMetaInfoDto, charaName).ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public async Task ApplyOwnDataToGposeTarget(CharaDataFullExtendedDto dataDto)
 | |
|     {
 | |
|         var chara = await _dalamudUtilService.GetGposeTargetGameObjectAsync().ConfigureAwait(false);
 | |
|         var charaName = chara?.Name.TextValue ?? string.Empty;
 | |
|         CharaDataDownloadDto downloadDto = new(dataDto.Id, dataDto.Uploader)
 | |
|         {
 | |
|             CustomizeData = dataDto.CustomizeData,
 | |
|             Description = dataDto.Description,
 | |
|             FileGamePaths = dataDto.FileGamePaths,
 | |
|             GlamourerData = dataDto.GlamourerData,
 | |
|             FileSwaps = dataDto.FileSwaps,
 | |
|             ManipulationData = dataDto.ManipulationData,
 | |
|             UpdatedDate = dataDto.UpdatedDate
 | |
|         };
 | |
| 
 | |
|         CharaDataMetaInfoDto metaInfoDto = new(dataDto.Id, dataDto.Uploader)
 | |
|         {
 | |
|             CanBeDownloaded = true,
 | |
|             Description = dataDto.Description,
 | |
|             PoseData = dataDto.PoseData,
 | |
|             UpdatedDate = dataDto.UpdatedDate,
 | |
|         };
 | |
| 
 | |
|         UiBlockingComputation = DataApplicationTask = DownloadAndAplyDataAsync(charaName, downloadDto, metaInfoDto, false);
 | |
|     }
 | |
| 
 | |
|     public Task ApplyPoseData(PoseEntry pose, string targetName)
 | |
|     {
 | |
|         return UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             if (string.IsNullOrEmpty(pose.PoseData) || !(await CanApplyInGpose().ConfigureAwait(false)).CanApply) return;
 | |
|             var gposeChara = await _dalamudUtilService.GetGposeCharacterFromObjectTableByNameAsync(targetName, true).ConfigureAwait(false);
 | |
|             if (gposeChara == null) return;
 | |
| 
 | |
|             var poseJson = Encoding.UTF8.GetString(LZ4Wrapper.Unwrap(Convert.FromBase64String(pose.PoseData)));
 | |
|             if (string.IsNullOrEmpty(poseJson)) return;
 | |
| 
 | |
|             await _ipcManager.Brio.SetPoseAsync(gposeChara.Address, poseJson).ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public Task ApplyPoseDataToGPoseTarget(PoseEntry pose)
 | |
|     {
 | |
|         return UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             var apply = await CanApplyInGpose().ConfigureAwait(false);
 | |
| 
 | |
|             if (apply.CanApply)
 | |
|             {
 | |
|                 await ApplyPoseData(pose, apply.TargetName).ConfigureAwait(false);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public Task ApplyWorldDataToTarget(PoseEntry pose, string targetName)
 | |
|     {
 | |
|         return UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             var apply = await CanApplyInGpose().ConfigureAwait(false);
 | |
|             if (pose.WorldData == default || !(await CanApplyInGpose().ConfigureAwait(false)).CanApply) return;
 | |
|             var gposeChara = await _dalamudUtilService.GetGposeCharacterFromObjectTableByNameAsync(targetName, true).ConfigureAwait(false);
 | |
|             if (gposeChara == null) return;
 | |
| 
 | |
|             if (pose.WorldData == null || pose.WorldData == default) return;
 | |
| 
 | |
|             Logger.LogDebug("Applying World data {data}", pose.WorldData);
 | |
| 
 | |
|             await _ipcManager.Brio.ApplyTransformAsync(gposeChara.Address, pose.WorldData.Value).ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public Task ApplyWorldDataToGPoseTarget(PoseEntry pose)
 | |
|     {
 | |
|         return UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             var apply = await CanApplyInGpose().ConfigureAwait(false);
 | |
|             if (apply.CanApply)
 | |
|             {
 | |
|                 await ApplyPoseData(pose, apply.TargetName).ConfigureAwait(false);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public void AttachWorldData(PoseEntry pose, CharaDataExtendedUpdateDto updateDto)
 | |
|     {
 | |
|         AttachingPoseTask = Task.Run(async () =>
 | |
|         {
 | |
|             ICharacter? playerChar = await _dalamudUtilService.GetPlayerCharacterAsync().ConfigureAwait(false);
 | |
|             if (playerChar == null) return;
 | |
|             if (_dalamudUtilService.IsInGpose)
 | |
|             {
 | |
|                 playerChar = await _dalamudUtilService.GetGposeCharacterFromObjectTableByNameAsync(playerChar.Name.TextValue, true).ConfigureAwait(false);
 | |
|             }
 | |
|             if (playerChar == null) return;
 | |
|             var worldData = await _ipcManager.Brio.GetTransformAsync(playerChar.Address).ConfigureAwait(false);
 | |
|             if (worldData == default) return;
 | |
| 
 | |
|             Logger.LogTrace("Attaching World data {data}", worldData);
 | |
| 
 | |
|             worldData.LocationInfo = await _dalamudUtilService.GetMapDataAsync().ConfigureAwait(false);
 | |
| 
 | |
|             Logger.LogTrace("World data serialized: {data}", worldData);
 | |
| 
 | |
|             pose.WorldData = worldData;
 | |
| 
 | |
|             updateDto.UpdatePoseList();
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public async Task<(bool CanApply, string TargetName)> CanApplyInGpose()
 | |
|     {
 | |
|         var obj = await _dalamudUtilService.GetGposeTargetGameObjectAsync().ConfigureAwait(false);
 | |
|         string targetName = string.Empty;
 | |
|         bool canApply = _dalamudUtilService.IsInGpose && obj != null
 | |
|             && obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player;
 | |
|         if (canApply)
 | |
|         {
 | |
|             targetName = obj!.Name.TextValue;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             targetName = "Invalid Target";
 | |
|         }
 | |
|         return (canApply, targetName);
 | |
|     }
 | |
| 
 | |
|     public void CancelDataApplication()
 | |
|     {
 | |
|         _applicationCts.Cancel();
 | |
|     }
 | |
| 
 | |
|     public void CancelUpload()
 | |
|     {
 | |
|         _uploadCts.Cancel();
 | |
|     }
 | |
| 
 | |
|     public void CreateCharaDataEntry(CancellationToken cancelToken)
 | |
|     {
 | |
|         UiBlockingComputation = DataCreationTask = Task.Run(async () =>
 | |
|         {
 | |
|             var result = await _apiController.CharaDataCreate().ConfigureAwait(false);
 | |
|             _ = Task.Run(async () =>
 | |
|             {
 | |
|                 _charaDataCreateCts = _charaDataCreateCts.CancelRecreate();
 | |
|                 using var ct = CancellationTokenSource.CreateLinkedTokenSource(_charaDataCreateCts.Token, cancelToken);
 | |
|                 await Task.Delay(TimeSpan.FromSeconds(10), ct.Token).ConfigureAwait(false);
 | |
|                 DataCreationTask = null;
 | |
|             });
 | |
| 
 | |
| 
 | |
|             if (result == null)
 | |
|                 return ("Failed to create character data, see log for more information", false);
 | |
| 
 | |
|             await AddOrUpdateDto(result).ConfigureAwait(false);
 | |
| 
 | |
|             return ("Created Character Data", true);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public async Task DeleteCharaData(CharaDataFullExtendedDto dto)
 | |
|     {
 | |
|         var ret = await _apiController.CharaDataDelete(dto.Id).ConfigureAwait(false);
 | |
|         if (ret)
 | |
|         {
 | |
|             _ownCharaData.Remove(dto.Id);
 | |
|             _metaInfoCache.Remove(dto.FullId, out _);
 | |
|         }
 | |
|         DistributeMetaInfo();
 | |
|     }
 | |
| 
 | |
|     public void DownloadMetaInfo(string importCode, bool store = true)
 | |
|     {
 | |
|         DownloadMetaInfoTask = Task.Run(async () =>
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 if (store)
 | |
|                 {
 | |
|                     LastDownloadedMetaInfo = null;
 | |
|                 }
 | |
|                 var metaInfo = await _apiController.CharaDataGetMetainfo(importCode).ConfigureAwait(false);
 | |
|                 _sharedMetaInfoTimeoutTasks[importCode] = Task.Delay(TimeSpan.FromSeconds(10));
 | |
|                 if (metaInfo == null)
 | |
|                 {
 | |
|                     _metaInfoCache[importCode] = null;
 | |
|                     return ("Failed to download meta info for this code. Check if the code is valid and you have rights to access it.", false);
 | |
|                 }
 | |
|                 await CacheData(metaInfo).ConfigureAwait(false);
 | |
|                 if (store)
 | |
|                 {
 | |
|                     LastDownloadedMetaInfo = await CharaDataMetaInfoExtendedDto.Create(metaInfo, _dalamudUtilService).ConfigureAwait(false);
 | |
|                 }
 | |
|                 return ("Ok", true);
 | |
|             }
 | |
|             finally
 | |
|             {
 | |
|                 if (!store)
 | |
|                     DownloadMetaInfoTask = null;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public async Task GetAllData(CancellationToken cancelToken)
 | |
|     {
 | |
|         foreach (var data in _ownCharaData)
 | |
|         {
 | |
|             _metaInfoCache.Remove(data.Key, out _);
 | |
|         }
 | |
|         _ownCharaData.Clear();
 | |
|         UiBlockingComputation = GetAllDataTask = Task.Run(async () =>
 | |
|         {
 | |
|             _getAllDataCts = _getAllDataCts.CancelRecreate();
 | |
|             var result = await _apiController.CharaDataGetOwn().ConfigureAwait(false);
 | |
| 
 | |
|             Initialized = true;
 | |
| 
 | |
|             if (result.Any())
 | |
|             {
 | |
|                 DataGetTimeoutTask = Task.Run(async () =>
 | |
|                 {
 | |
|                     using var ct = CancellationTokenSource.CreateLinkedTokenSource(_getAllDataCts.Token, cancelToken);
 | |
| #if !DEBUG
 | |
|                     await Task.Delay(TimeSpan.FromMinutes(1), ct.Token).ConfigureAwait(false);
 | |
| #else
 | |
|                     await Task.Delay(TimeSpan.FromSeconds(5), ct.Token).ConfigureAwait(false);
 | |
| #endif
 | |
|                 });
 | |
|             }
 | |
| 
 | |
|             return result.OrderBy(u => u.CreatedDate).Select(k => new CharaDataFullExtendedDto(k)).ToList();
 | |
|         });
 | |
| 
 | |
|         var result = await GetAllDataTask.ConfigureAwait(false);
 | |
|         foreach (var item in result)
 | |
|         {
 | |
|             await AddOrUpdateDto(item).ConfigureAwait(false);
 | |
|         }
 | |
| 
 | |
|         foreach (var id in _updateDtos.Keys.Where(r => !result.Exists(res => string.Equals(res.Id, r, StringComparison.Ordinal))).ToList())
 | |
|         {
 | |
|             _updateDtos.Remove(id);
 | |
|         }
 | |
|         GetAllDataTask = null;
 | |
|     }
 | |
| 
 | |
|     public async Task GetAllSharedData(CancellationToken token)
 | |
|     {
 | |
|         Logger.LogDebug("Getting Shared with You Data");
 | |
| 
 | |
|         UiBlockingComputation = GetSharedWithYouTask = _apiController.CharaDataGetShared();
 | |
|         _sharedWithYouData.Clear();
 | |
| 
 | |
|         GetSharedWithYouTimeoutTask = Task.Run(async () =>
 | |
|         {
 | |
|             _getSharedDataCts = _getSharedDataCts.CancelRecreate();
 | |
|             using var ct = CancellationTokenSource.CreateLinkedTokenSource(_getSharedDataCts.Token, token);
 | |
| #if !DEBUG
 | |
|             await Task.Delay(TimeSpan.FromMinutes(1), ct.Token).ConfigureAwait(false);
 | |
| #else
 | |
|             await Task.Delay(TimeSpan.FromSeconds(5), ct.Token).ConfigureAwait(false);
 | |
| #endif
 | |
|             GetSharedWithYouTimeoutTask = null;
 | |
|             Logger.LogDebug("Finished Shared with You Data Timeout");
 | |
|         });
 | |
| 
 | |
|         var result = await GetSharedWithYouTask.ConfigureAwait(false);
 | |
|         foreach (var grouping in result.GroupBy(r => r.Uploader))
 | |
|         {
 | |
|             var pair = _pairManager.GetPairByUID(grouping.Key.UID);
 | |
|             if (pair?.IsPaused ?? false) continue;
 | |
|             List<CharaDataMetaInfoExtendedDto> newList = new();
 | |
|             foreach (var item in grouping)
 | |
|             {
 | |
|                 var extended = await CharaDataMetaInfoExtendedDto.Create(item, _dalamudUtilService).ConfigureAwait(false);
 | |
|                 newList.Add(extended);
 | |
|                 CacheData(extended);
 | |
|             }
 | |
|             _sharedWithYouData[grouping.Key] = newList;
 | |
|         }
 | |
| 
 | |
|         DistributeMetaInfo();
 | |
| 
 | |
|         Logger.LogDebug("Finished getting Shared with You Data");
 | |
|         GetSharedWithYouTask = null;
 | |
|     }
 | |
| 
 | |
|     public CharaDataExtendedUpdateDto? GetUpdateDto(string id)
 | |
|     {
 | |
|         if (_updateDtos.TryGetValue(id, out var dto))
 | |
|             return dto;
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     public bool IsInTimeout(string key)
 | |
|     {
 | |
|         if (!_sharedMetaInfoTimeoutTasks.TryGetValue(key, out var task)) return false;
 | |
|         return !task?.IsCompleted ?? false;
 | |
|     }
 | |
| 
 | |
|     public void LoadMcdf(string filePath)
 | |
|     {
 | |
|         LoadedMcdfHeader = _fileHandler.LoadCharaFileHeader(filePath);
 | |
|     }
 | |
| 
 | |
|     public void McdfApplyToTarget(string charaName)
 | |
|     {
 | |
|         if (LoadedMcdfHeader == null || !LoadedMcdfHeader.IsCompletedSuccessfully) return;
 | |
| 
 | |
|         List<string> actuallyExtractedFiles = [];
 | |
| 
 | |
|         UiBlockingComputation = McdfApplicationTask = Task.Run(async () =>
 | |
|         {
 | |
|             Guid applicationId = Guid.NewGuid();
 | |
|             try
 | |
|             {
 | |
|                 using GameObjectHandler? tempHandler = await _characterHandler.TryCreateGameObjectHandler(charaName, true).ConfigureAwait(false);
 | |
|                 if (tempHandler == null) return;
 | |
|                 var playerChar = await _dalamudUtilService.GetPlayerCharacterAsync().ConfigureAwait(false);
 | |
|                 bool isSelf = playerChar != null && string.Equals(playerChar.Name.TextValue, tempHandler.Name, StringComparison.Ordinal);
 | |
| 
 | |
|                 long expectedExtractedSize = LoadedMcdfHeader.Result.ExpectedLength;
 | |
|                 var charaFile = LoadedMcdfHeader.Result.LoadedFile;
 | |
|                 DataApplicationProgress = "Extracting MCDF data";
 | |
| 
 | |
|                 var extractedFiles = _fileHandler.McdfExtractFiles(charaFile, expectedExtractedSize, actuallyExtractedFiles);
 | |
| 
 | |
|                 foreach (var entry in charaFile.CharaFileData.FileSwaps.SelectMany(k => k.GamePaths, (k, p) => new KeyValuePair<string, string>(p, k.FileSwapPath)))
 | |
|                 {
 | |
|                     extractedFiles[entry.Key] = entry.Value;
 | |
|                 }
 | |
| 
 | |
|                 DataApplicationProgress = "Applying MCDF data";
 | |
| 
 | |
|                 var extended = await CharaDataMetaInfoExtendedDto.Create(new(charaFile.FilePath, new UserData(string.Empty)), _dalamudUtilService)
 | |
|                     .ConfigureAwait(false);
 | |
|                 await ApplyDataAsync(applicationId, tempHandler, isSelf, autoRevert: false, extended,
 | |
|                     extractedFiles, charaFile.CharaFileData.ManipulationData, charaFile.CharaFileData.GlamourerData,
 | |
|                     charaFile.CharaFileData.CustomizePlusData, CancellationToken.None).ConfigureAwait(false);
 | |
|             }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 Logger.LogWarning(ex, "Failed to extract MCDF");
 | |
|                 throw;
 | |
|             }
 | |
|             finally
 | |
|             {
 | |
|                 // delete extracted files
 | |
|                 foreach (var file in actuallyExtractedFiles)
 | |
|                 {
 | |
|                     File.Delete(file);
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public async Task McdfApplyToGposeTarget()
 | |
|     {
 | |
|         var apply = await CanApplyInGpose().ConfigureAwait(false);
 | |
|         if (apply.CanApply)
 | |
|         {
 | |
|             McdfApplyToTarget(apply.TargetName);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void SaveMareCharaFile(string description, string filePath)
 | |
|     {
 | |
|         UiBlockingComputation = Task.Run(async () => await _fileHandler.SaveCharaFileAsync(description, filePath).ConfigureAwait(false));
 | |
|     }
 | |
| 
 | |
|     public void SetAppearanceData(string dtoId)
 | |
|     {
 | |
|         var hasDto = _ownCharaData.TryGetValue(dtoId, out var dto);
 | |
|         if (!hasDto || dto == null) return;
 | |
| 
 | |
|         var hasUpdateDto = _updateDtos.TryGetValue(dtoId, out var updateDto);
 | |
|         if (!hasUpdateDto || updateDto == null) return;
 | |
| 
 | |
|         UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             await _fileHandler.UpdateCharaDataAsync(updateDto).ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public Task<HandledCharaDataEntry?> SpawnAndApplyData(CharaDataDownloadDto charaDataDownloadDto)
 | |
|     {
 | |
|         var task = Task.Run(async () =>
 | |
|         {
 | |
|             var newActor = await _ipcManager.Brio.SpawnActorAsync().ConfigureAwait(false);
 | |
|             if (newActor == null) return null;
 | |
|             await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
 | |
| 
 | |
|             await ApplyCharaData(charaDataDownloadDto, newActor.Name.TextValue).ConfigureAwait(false);
 | |
| 
 | |
|             return _characterHandler.HandledCharaData.GetValueOrDefault(newActor.Name.TextValue);
 | |
|         });
 | |
|         UiBlockingComputation = task;
 | |
|         return task;
 | |
|     }
 | |
| 
 | |
|     public Task<HandledCharaDataEntry?> SpawnAndApplyData(CharaDataMetaInfoDto charaDataMetaInfoDto)
 | |
|     {
 | |
|         var task = Task.Run(async () =>
 | |
|         {
 | |
|             var newActor = await _ipcManager.Brio.SpawnActorAsync().ConfigureAwait(false);
 | |
|             if (newActor == null) return null;
 | |
|             await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
 | |
| 
 | |
|             await ApplyCharaData(charaDataMetaInfoDto, newActor.Name.TextValue).ConfigureAwait(false);
 | |
| 
 | |
|             return _characterHandler.HandledCharaData.GetValueOrDefault(newActor.Name.TextValue);
 | |
|         });
 | |
|         UiBlockingComputation = task;
 | |
|         return task;
 | |
|     }
 | |
| 
 | |
|     private async Task<CharaDataMetaInfoExtendedDto> CacheData(CharaDataFullExtendedDto ownCharaData)
 | |
|     {
 | |
|         var metaInfo = new CharaDataMetaInfoDto(ownCharaData.Id, ownCharaData.Uploader)
 | |
|         {
 | |
|             Description = ownCharaData.Description,
 | |
|             UpdatedDate = ownCharaData.UpdatedDate,
 | |
|             CanBeDownloaded = !string.IsNullOrEmpty(ownCharaData.GlamourerData) && (ownCharaData.OriginalFiles.Count == ownCharaData.FileGamePaths.Count),
 | |
|             PoseData = ownCharaData.PoseData,
 | |
|         };
 | |
| 
 | |
|         var extended = await CharaDataMetaInfoExtendedDto.Create(metaInfo, _dalamudUtilService, isOwnData: true).ConfigureAwait(false);
 | |
|         _metaInfoCache[extended.FullId] = extended;
 | |
|         DistributeMetaInfo();
 | |
| 
 | |
|         return extended;
 | |
|     }
 | |
| 
 | |
|     private async Task<CharaDataMetaInfoExtendedDto> CacheData(CharaDataMetaInfoDto metaInfo, bool isOwnData = false)
 | |
|     {
 | |
|         var extended = await CharaDataMetaInfoExtendedDto.Create(metaInfo, _dalamudUtilService, isOwnData).ConfigureAwait(false);
 | |
|         _metaInfoCache[extended.FullId] = extended;
 | |
|         DistributeMetaInfo();
 | |
| 
 | |
|         return extended;
 | |
|     }
 | |
| 
 | |
|     private readonly SemaphoreSlim _distributionSemaphore = new(1, 1);
 | |
| 
 | |
|     private void DistributeMetaInfo()
 | |
|     {
 | |
|         _distributionSemaphore.Wait();
 | |
|         _nearbyManager.UpdateSharedData(_metaInfoCache.ToDictionary());
 | |
|         _characterHandler.UpdateHandledData(_metaInfoCache.ToDictionary());
 | |
|         _distributionSemaphore.Release();
 | |
|     }
 | |
| 
 | |
|     private void CacheData(CharaDataMetaInfoExtendedDto charaData)
 | |
|     {
 | |
|         _metaInfoCache[charaData.FullId] = charaData;
 | |
|     }
 | |
| 
 | |
|     public bool TryGetMetaInfo(string key, out CharaDataMetaInfoExtendedDto? metaInfo)
 | |
|     {
 | |
|         return _metaInfoCache.TryGetValue(key, out metaInfo);
 | |
|     }
 | |
| 
 | |
|     public void UploadCharaData(string id)
 | |
|     {
 | |
|         var hasUpdateDto = _updateDtos.TryGetValue(id, out var updateDto);
 | |
|         if (!hasUpdateDto || updateDto == null) return;
 | |
| 
 | |
|         UiBlockingComputation = CharaUpdateTask = CharaUpdateAsync(updateDto);
 | |
|     }
 | |
| 
 | |
|     public void UploadMissingFiles(string id)
 | |
|     {
 | |
|         var hasDto = _ownCharaData.TryGetValue(id, out var dto);
 | |
|         if (!hasDto || dto == null) return;
 | |
| 
 | |
|         UiBlockingComputation = UploadTask = RestoreThenUpload(dto);
 | |
|     }
 | |
| 
 | |
|     private async Task<(string Output, bool Success)> RestoreThenUpload(CharaDataFullExtendedDto dto)
 | |
|     {
 | |
|         var newDto = await _apiController.CharaDataAttemptRestore(dto.Id).ConfigureAwait(false);
 | |
|         if (newDto == null)
 | |
|         {
 | |
|             _ownCharaData.Remove(dto.Id);
 | |
|             _metaInfoCache.Remove(dto.FullId, out _);
 | |
|             UiBlockingComputation = null;
 | |
|             return ("No such DTO found", false);
 | |
|         }
 | |
| 
 | |
|         await AddOrUpdateDto(newDto).ConfigureAwait(false);
 | |
|         _ = _ownCharaData.TryGetValue(dto.Id, out var extendedDto);
 | |
| 
 | |
|         if (!extendedDto!.HasMissingFiles)
 | |
|         {
 | |
|             UiBlockingComputation = null;
 | |
|             return ("Restored successfully", true);
 | |
|         }
 | |
| 
 | |
|         var missingFileList = extendedDto!.MissingFiles.ToList();
 | |
|         var result = await UploadFiles(missingFileList, async () =>
 | |
|         {
 | |
|             var newFilePaths = dto.FileGamePaths;
 | |
|             foreach (var missing in missingFileList)
 | |
|             {
 | |
|                 newFilePaths.Add(missing);
 | |
|             }
 | |
|             CharaDataUpdateDto updateDto = new(dto.Id)
 | |
|             {
 | |
|                 FileGamePaths = newFilePaths
 | |
|             };
 | |
|             var res = await _apiController.CharaDataUpdate(updateDto).ConfigureAwait(false);
 | |
|             await AddOrUpdateDto(res).ConfigureAwait(false);
 | |
|         }).ConfigureAwait(false);
 | |
| 
 | |
|         UiBlockingComputation = null;
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     internal void ApplyDataToSelf(CharaDataFullExtendedDto dataDto)
 | |
|     {
 | |
|         var chara = _dalamudUtilService.GetPlayerName();
 | |
|         CharaDataDownloadDto downloadDto = new(dataDto.Id, dataDto.Uploader)
 | |
|         {
 | |
|             CustomizeData = dataDto.CustomizeData,
 | |
|             Description = dataDto.Description,
 | |
|             FileGamePaths = dataDto.FileGamePaths,
 | |
|             GlamourerData = dataDto.GlamourerData,
 | |
|             FileSwaps = dataDto.FileSwaps,
 | |
|             ManipulationData = dataDto.ManipulationData,
 | |
|             UpdatedDate = dataDto.UpdatedDate
 | |
|         };
 | |
| 
 | |
|         CharaDataMetaInfoDto metaInfoDto = new(dataDto.Id, dataDto.Uploader)
 | |
|         {
 | |
|             CanBeDownloaded = true,
 | |
|             Description = dataDto.Description,
 | |
|             PoseData = dataDto.PoseData,
 | |
|             UpdatedDate = dataDto.UpdatedDate,
 | |
|         };
 | |
| 
 | |
|         UiBlockingComputation = DataApplicationTask = DownloadAndAplyDataAsync(chara, downloadDto, metaInfoDto);
 | |
|     }
 | |
| 
 | |
|     internal void AttachPoseData(PoseEntry pose, CharaDataExtendedUpdateDto updateDto)
 | |
|     {
 | |
|         AttachingPoseTask = Task.Run(async () =>
 | |
|         {
 | |
|             ICharacter? playerChar = await _dalamudUtilService.GetPlayerCharacterAsync().ConfigureAwait(false);
 | |
|             if (playerChar == null) return;
 | |
|             if (_dalamudUtilService.IsInGpose)
 | |
|             {
 | |
|                 playerChar = await _dalamudUtilService.GetGposeCharacterFromObjectTableByNameAsync(playerChar.Name.TextValue, true).ConfigureAwait(false);
 | |
|             }
 | |
|             if (playerChar == null) return;
 | |
|             var poseData = await _ipcManager.Brio.GetPoseAsync(playerChar.Address).ConfigureAwait(false);
 | |
|             if (poseData == null) return;
 | |
| 
 | |
|             var compressedByteData = LZ4Wrapper.WrapHC(Encoding.UTF8.GetBytes(poseData));
 | |
|             pose.PoseData = Convert.ToBase64String(compressedByteData);
 | |
|             updateDto.UpdatePoseList();
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     internal void McdfSpawnApplyToGposeTarget()
 | |
|     {
 | |
|         UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             var newActor = await _ipcManager.Brio.SpawnActorAsync().ConfigureAwait(false);
 | |
|             if (newActor == null) return;
 | |
|             await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
 | |
|             unsafe
 | |
|             {
 | |
|                 _dalamudUtilService.GposeTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)newActor.Address;
 | |
|             }
 | |
| 
 | |
|             await McdfApplyToGposeTarget().ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     internal void ApplyFullPoseDataToTarget(PoseEntry value, string targetName)
 | |
|     {
 | |
|         UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             await ApplyPoseData(value, targetName).ConfigureAwait(false);
 | |
|             await ApplyWorldDataToTarget(value, targetName).ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     internal void ApplyFullPoseDataToGposeTarget(PoseEntry value)
 | |
|     {
 | |
|         UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             var apply = await CanApplyInGpose().ConfigureAwait(false);
 | |
|             if (apply.CanApply)
 | |
|             {
 | |
|                 await ApplyPoseData(value, apply.TargetName).ConfigureAwait(false);
 | |
|                 await ApplyWorldDataToTarget(value, apply.TargetName).ConfigureAwait(false);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     internal void SpawnAndApplyWorldTransform(CharaDataMetaInfoDto metaInfo, PoseEntry value)
 | |
|     {
 | |
|         UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             var actor = await SpawnAndApplyData(metaInfo).ConfigureAwait(false);
 | |
|             if (actor == null) return;
 | |
|             await ApplyPoseData(value, actor.Name).ConfigureAwait(false);
 | |
|             await ApplyWorldDataToTarget(value, actor.Name).ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     internal unsafe void TargetGposeActor(HandledCharaDataEntry actor)
 | |
|     {
 | |
|         var gposeActor = _dalamudUtilService.GetGposeCharacterFromObjectTableByName(actor.Name, true);
 | |
|         if (gposeActor != null)
 | |
|         {
 | |
|             _dalamudUtilService.GposeTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gposeActor.Address;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     protected override void Dispose(bool disposing)
 | |
|     {
 | |
|         base.Dispose(disposing);
 | |
|         if (disposing)
 | |
|         {
 | |
|             _getAllDataCts?.Cancel();
 | |
|             _getAllDataCts?.Dispose();
 | |
|             _getSharedDataCts?.Cancel();
 | |
|             _getSharedDataCts?.Dispose();
 | |
|             _charaDataCreateCts?.Cancel();
 | |
|             _charaDataCreateCts?.Dispose();
 | |
|             _uploadCts?.Cancel();
 | |
|             _uploadCts?.Dispose();
 | |
|             _applicationCts.Cancel();
 | |
|             _applicationCts.Dispose();
 | |
|             _connectCts?.Cancel();
 | |
|             _connectCts?.Dispose();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private async Task AddOrUpdateDto(CharaDataFullDto? dto)
 | |
|     {
 | |
|         if (dto == null) return;
 | |
| 
 | |
|         _ownCharaData[dto.Id] = new(dto);
 | |
|         _updateDtos[dto.Id] = new(new(dto.Id), _ownCharaData[dto.Id]);
 | |
| 
 | |
|         await CacheData(_ownCharaData[dto.Id]).ConfigureAwait(false);
 | |
|     }
 | |
| 
 | |
|     private async Task ApplyDataAsync(Guid applicationId, GameObjectHandler tempHandler, bool isSelf, bool autoRevert,
 | |
|         CharaDataMetaInfoExtendedDto metaInfo, Dictionary<string, string> modPaths, string? manipData, string? glamourerData, string? customizeData, CancellationToken token)
 | |
|     {
 | |
|         Guid? cPlusId = null;
 | |
|         Guid penumbraCollection;
 | |
|         try
 | |
|         {
 | |
|             DataApplicationProgress = "Reverting previous Application";
 | |
| 
 | |
|             Logger.LogTrace("[{appId}] Reverting chara {chara}", applicationId, tempHandler.Name);
 | |
|             bool reverted = await _characterHandler.RevertHandledChara(tempHandler.Name).ConfigureAwait(false);
 | |
|             if (reverted)
 | |
|                 await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
 | |
| 
 | |
|             Logger.LogTrace("[{appId}] Applying data in Penumbra", applicationId);
 | |
| 
 | |
|             DataApplicationProgress = "Applying Penumbra information";
 | |
|             penumbraCollection = await _ipcManager.Penumbra.CreateTemporaryCollectionAsync(Logger, metaInfo.Uploader.UID + metaInfo.Id).ConfigureAwait(false);
 | |
|             var idx = await _dalamudUtilService.RunOnFrameworkThread(() => tempHandler.GetGameObject()?.ObjectIndex).ConfigureAwait(false) ?? 0;
 | |
|             await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(Logger, penumbraCollection, idx).ConfigureAwait(false);
 | |
|             await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, applicationId, penumbraCollection, modPaths).ConfigureAwait(false);
 | |
|             await _ipcManager.Penumbra.SetManipulationDataAsync(Logger, applicationId, penumbraCollection, manipData ?? string.Empty).ConfigureAwait(false);
 | |
| 
 | |
|             Logger.LogTrace("[{appId}] Applying Glamourer data and Redrawing", applicationId);
 | |
|             DataApplicationProgress = "Applying Glamourer and redrawing Character";
 | |
|             await _ipcManager.Glamourer.ApplyAllAsync(Logger, tempHandler, glamourerData, applicationId, token).ConfigureAwait(false);
 | |
|             await _ipcManager.Penumbra.RedrawAsync(Logger, tempHandler, applicationId, token).ConfigureAwait(false);
 | |
|             await _dalamudUtilService.WaitWhileCharacterIsDrawing(Logger, tempHandler, applicationId, ct: token).ConfigureAwait(false);
 | |
|             Logger.LogTrace("[{appId}] Removing collection", applicationId);
 | |
|             await _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, penumbraCollection).ConfigureAwait(false);
 | |
| 
 | |
|             DataApplicationProgress = "Applying Customize+ data";
 | |
|             Logger.LogTrace("[{appId}] Appplying C+ data", applicationId);
 | |
| 
 | |
|             if (!string.IsNullOrEmpty(customizeData))
 | |
|             {
 | |
|                 cPlusId = await _ipcManager.CustomizePlus.SetBodyScaleAsync(tempHandler.Address, customizeData).ConfigureAwait(false);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 cPlusId = await _ipcManager.CustomizePlus.SetBodyScaleAsync(tempHandler.Address, Convert.ToBase64String(Encoding.UTF8.GetBytes("{}"))).ConfigureAwait(false);
 | |
|             }
 | |
| 
 | |
|             if (autoRevert)
 | |
|             {
 | |
|                 Logger.LogTrace("[{appId}] Starting wait for auto revert", applicationId);
 | |
| 
 | |
|                 int i = 15;
 | |
|                 while (i > 0)
 | |
|                 {
 | |
|                     DataApplicationProgress = $"All data applied. Reverting automatically in {i} seconds.";
 | |
|                     await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
 | |
|                     i--;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 Logger.LogTrace("[{appId}] Adding {name} to handled objects", applicationId, tempHandler.Name);
 | |
| 
 | |
|                 _characterHandler.AddHandledChara(new HandledCharaDataEntry(tempHandler.Name, isSelf, cPlusId, metaInfo));
 | |
|             }
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             if (token.IsCancellationRequested)
 | |
|                 DataApplicationProgress = "Application aborted. Reverting Character...";
 | |
|             else if (autoRevert)
 | |
|                 DataApplicationProgress = "Application finished. Reverting Character...";
 | |
|             if (autoRevert)
 | |
|             {
 | |
|                 await _characterHandler.RevertChara(tempHandler.Name, cPlusId).ConfigureAwait(false);
 | |
|             }
 | |
| 
 | |
|             if (!_dalamudUtilService.IsInGpose)
 | |
|                 Mediator.Publish(new HaltCharaDataCreation(Resume: true));
 | |
| 
 | |
|             if (metaInfo != null && _configService.Current.FavoriteCodes.TryGetValue(metaInfo.Uploader.UID + ":" + metaInfo.Id, out var favorite) && favorite != null)
 | |
|             {
 | |
|                 favorite.LastDownloaded = DateTime.UtcNow;
 | |
|                 _configService.Save();
 | |
|             }
 | |
| 
 | |
|             DataApplicationTask = null;
 | |
|             DataApplicationProgress = string.Empty;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private async Task CharaUpdateAsync(CharaDataExtendedUpdateDto updateDto)
 | |
|     {
 | |
|         Logger.LogDebug("Uploading Chara Data to Server");
 | |
|         var baseUpdateDto = updateDto.BaseDto;
 | |
|         if (baseUpdateDto.FileGamePaths != null)
 | |
|         {
 | |
|             Logger.LogDebug("Detected file path changes, starting file upload");
 | |
| 
 | |
|             UploadTask = UploadFiles(baseUpdateDto.FileGamePaths);
 | |
|             var result = await UploadTask.ConfigureAwait(false);
 | |
|             if (!result.Success)
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         Logger.LogDebug("Pushing update dto to server: {data}", baseUpdateDto);
 | |
| 
 | |
|         var res = await _apiController.CharaDataUpdate(baseUpdateDto).ConfigureAwait(false);
 | |
|         await AddOrUpdateDto(res).ConfigureAwait(false);
 | |
|         CharaUpdateTask = null;
 | |
|     }
 | |
| 
 | |
|     private async Task DownloadAndAplyDataAsync(string charaName, CharaDataDownloadDto charaDataDownloadDto, CharaDataMetaInfoDto metaInfo, bool autoRevert = true)
 | |
|     {
 | |
|         _applicationCts = _applicationCts.CancelRecreate();
 | |
|         var token = _applicationCts.Token;
 | |
|         ICharacter? chara = (await _dalamudUtilService.GetGposeCharacterFromObjectTableByNameAsync(charaName, _dalamudUtilService.IsInGpose).ConfigureAwait(false));
 | |
| 
 | |
|         if (chara == null)
 | |
|             return;
 | |
| 
 | |
|         var applicationId = Guid.NewGuid();
 | |
| 
 | |
|         var playerChar = await _dalamudUtilService.GetPlayerCharacterAsync().ConfigureAwait(false);
 | |
|         bool isSelf = playerChar != null && string.Equals(playerChar.Name.TextValue, chara.Name.TextValue, StringComparison.Ordinal);
 | |
| 
 | |
|         DataApplicationProgress = "Checking local files";
 | |
| 
 | |
|         Logger.LogTrace("[{appId}] Computing local missing files", applicationId);
 | |
| 
 | |
|         Dictionary<string, string> modPaths;
 | |
|         List<FileReplacementData> missingFiles;
 | |
|         _fileHandler.ComputeMissingFiles(charaDataDownloadDto, out modPaths, out missingFiles);
 | |
| 
 | |
|         Logger.LogTrace("[{appId}] Computing local missing files", applicationId);
 | |
| 
 | |
|         using GameObjectHandler? tempHandler = await _characterHandler.TryCreateGameObjectHandler(chara.ObjectIndex).ConfigureAwait(false);
 | |
|         if (tempHandler == null) return;
 | |
| 
 | |
|         if (missingFiles.Any())
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 DataApplicationProgress = "Downloading Missing Files. Please be patient.";
 | |
|                 await _fileHandler.DownloadFilesAsync(tempHandler, missingFiles, modPaths, token).ConfigureAwait(false);
 | |
|             }
 | |
|             catch (FileNotFoundException)
 | |
|             {
 | |
|                 DataApplicationProgress = "Failed to download one or more files. Aborting.";
 | |
|                 DataApplicationTask = null;
 | |
|                 return;
 | |
|             }
 | |
|             catch (OperationCanceledException)
 | |
|             {
 | |
|                 DataApplicationProgress = "Application aborted.";
 | |
|                 DataApplicationTask = null;
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!_dalamudUtilService.IsInGpose)
 | |
|             Mediator.Publish(new HaltCharaDataCreation());
 | |
| 
 | |
|         var extendedMetaInfo = await CacheData(metaInfo).ConfigureAwait(false);
 | |
| 
 | |
|         await ApplyDataAsync(applicationId, tempHandler, isSelf, autoRevert, extendedMetaInfo, modPaths, charaDataDownloadDto.ManipulationData, charaDataDownloadDto.GlamourerData,
 | |
|             charaDataDownloadDto.CustomizeData, token).ConfigureAwait(false);
 | |
|     }
 | |
| 
 | |
|     public async Task<(string Result, bool Success)> UploadFiles(List<GamePathEntry> missingFileList, Func<Task>? postUpload = null)
 | |
|     {
 | |
|         UploadProgress = new ValueProgress<string>();
 | |
|         try
 | |
|         {
 | |
|             _uploadCts = _uploadCts.CancelRecreate();
 | |
|             var missingFiles = await _fileHandler.UploadFiles([.. missingFileList.Select(k => k.HashOrFileSwap)], UploadProgress, _uploadCts.Token).ConfigureAwait(false);
 | |
|             if (missingFiles.Any())
 | |
|             {
 | |
|                 Logger.LogInformation("Failed to upload {files}", string.Join(", ", missingFiles));
 | |
|                 return ($"Upload failed: {missingFiles.Count} missing or forbidden to upload local files.", false);
 | |
|             }
 | |
| 
 | |
|             if (postUpload != null)
 | |
|                 await postUpload.Invoke().ConfigureAwait(false);
 | |
| 
 | |
|             return ("Upload sucessful", true);
 | |
|         }
 | |
|         catch (Exception ex)
 | |
|         {
 | |
|             Logger.LogWarning(ex, "Error during upload");
 | |
|             if (ex is OperationCanceledException)
 | |
|             {
 | |
|                 return ("Upload Cancelled", false);
 | |
|             }
 | |
|             return ("Error in upload, see log for more details", false);
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             UiBlockingComputation = null;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void RevertChara(HandledCharaDataEntry? handled)
 | |
|     {
 | |
|         UiBlockingComputation = _characterHandler.RevertHandledChara(handled);
 | |
|     }
 | |
| 
 | |
|     internal void RemoveChara(string handledActor)
 | |
|     {
 | |
|         if (string.IsNullOrEmpty(handledActor)) return;
 | |
|         UiBlockingComputation = Task.Run(async () =>
 | |
|         {
 | |
|             await _characterHandler.RevertHandledChara(handledActor).ConfigureAwait(false);
 | |
|             var gposeChara = await _dalamudUtilService.GetGposeCharacterFromObjectTableByNameAsync(handledActor, true).ConfigureAwait(false);
 | |
|             if (gposeChara != null)
 | |
|                 await _ipcManager.Brio.DespawnActorAsync(gposeChara.Address).ConfigureAwait(false);
 | |
|         });
 | |
|     }
 | |
| }
 | 
