Compare commits
4 Commits
compressio
...
lifestream
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de9c9955ef | ||
|
|
df33a0f0a2 | ||
| c439d1c822 | |||
|
|
fb58d8657d |
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Lifestream.Enums;
|
||||||
|
|
||||||
|
public enum ResidentialAetheryteKind
|
||||||
|
{
|
||||||
|
None = -1,
|
||||||
|
Uldah = 9,
|
||||||
|
Gridania = 2,
|
||||||
|
Limsa = 8,
|
||||||
|
Foundation = 70,
|
||||||
|
Kugane = 111,
|
||||||
|
}
|
||||||
1
LightlessSync/Interop/InteropModel/GlobalModels.cs
Normal file
1
LightlessSync/Interop/InteropModel/GlobalModels.cs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
global using AddressBookEntryTuple = (string Name, int World, int City, int Ward, int PropertyType, int Plot, int Apartment, bool ApartmentSubdivision, bool AliasEnabled, string Alias);
|
||||||
129
LightlessSync/Interop/Ipc/IpcCallerLifestream.cs
Normal file
129
LightlessSync/Interop/Ipc/IpcCallerLifestream.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using Lifestream.Enums;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
||||||
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
|
public sealed class IpcCallerLifestream : IpcServiceBase
|
||||||
|
{
|
||||||
|
private static readonly IpcServiceDescriptor LifestreamDescriptor = new("Lifestream", "Lifestream", new Version(0, 0, 0, 0));
|
||||||
|
|
||||||
|
private readonly ICallGateSubscriber<string, object> _executeLifestreamCommand;
|
||||||
|
private readonly ICallGateSubscriber<AddressBookEntryTuple, bool> _isHere;
|
||||||
|
private readonly ICallGateSubscriber<AddressBookEntryTuple, object> _goToHousingAddress;
|
||||||
|
private readonly ICallGateSubscriber<bool> _isBusy;
|
||||||
|
private readonly ICallGateSubscriber<object> _abort;
|
||||||
|
private readonly ICallGateSubscriber<string, bool> _changeWorld;
|
||||||
|
private readonly ICallGateSubscriber<uint, bool> _changeWorldById;
|
||||||
|
private readonly ICallGateSubscriber<string, bool> _aetheryteTeleport;
|
||||||
|
private readonly ICallGateSubscriber<uint, bool> _aetheryteTeleportById;
|
||||||
|
private readonly ICallGateSubscriber<bool> _canChangeInstance;
|
||||||
|
private readonly ICallGateSubscriber<int> _getCurrentInstance;
|
||||||
|
private readonly ICallGateSubscriber<int> _getNumberOfInstances;
|
||||||
|
private readonly ICallGateSubscriber<int, object> _changeInstance;
|
||||||
|
private readonly ICallGateSubscriber<(ResidentialAetheryteKind, int, int)> _getCurrentPlotInfo;
|
||||||
|
|
||||||
|
public IpcCallerLifestream(IDalamudPluginInterface pi, LightlessMediator lightlessMediator, ILogger<IpcCallerLifestream> logger)
|
||||||
|
: base(logger, lightlessMediator, pi, LifestreamDescriptor)
|
||||||
|
{
|
||||||
|
_executeLifestreamCommand = pi.GetIpcSubscriber<string, object>("Lifestream.ExecuteCommand");
|
||||||
|
_isHere = pi.GetIpcSubscriber<AddressBookEntryTuple, bool>("Lifestream.IsHere");
|
||||||
|
_goToHousingAddress = pi.GetIpcSubscriber<AddressBookEntryTuple, object>("Lifestream.GoToHousingAddress");
|
||||||
|
_isBusy = pi.GetIpcSubscriber<bool>("Lifestream.IsBusy");
|
||||||
|
_abort = pi.GetIpcSubscriber<object>("Lifestream.Abort");
|
||||||
|
_changeWorld = pi.GetIpcSubscriber<string, bool>("Lifestream.ChangeWorld");
|
||||||
|
_changeWorldById = pi.GetIpcSubscriber<uint, bool>("Lifestream.ChangeWorldById");
|
||||||
|
_aetheryteTeleport = pi.GetIpcSubscriber<string, bool>("Lifestream.AetheryteTeleport");
|
||||||
|
_aetheryteTeleportById = pi.GetIpcSubscriber<uint, bool>("Lifestream.AetheryteTeleportById");
|
||||||
|
_canChangeInstance = pi.GetIpcSubscriber<bool>("Lifestream.CanChangeInstance");
|
||||||
|
_getCurrentInstance = pi.GetIpcSubscriber<int>("Lifestream.GetCurrentInstance");
|
||||||
|
_getNumberOfInstances = pi.GetIpcSubscriber<int>("Lifestream.GetNumberOfInstances");
|
||||||
|
_changeInstance = pi.GetIpcSubscriber<int, object>("Lifestream.ChangeInstance");
|
||||||
|
_getCurrentPlotInfo = pi.GetIpcSubscriber<(ResidentialAetheryteKind, int, int)>("Lifestream.GetCurrentPlotInfo");
|
||||||
|
CheckAPI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteLifestreamCommand(string command)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return;
|
||||||
|
_executeLifestreamCommand.InvokeAction(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHere(AddressBookEntryTuple entry)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _isHere.InvokeFunc(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoToHousingAddress(AddressBookEntryTuple entry)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return;
|
||||||
|
_goToHousingAddress.InvokeAction(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBusy()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _isBusy.InvokeFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Abort()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return;
|
||||||
|
_abort.InvokeAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChangeWorld(string worldName)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _changeWorld.InvokeFunc(worldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AetheryteTeleport(string aetheryteName)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _aetheryteTeleport.InvokeFunc(aetheryteName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChangeWorldById(uint worldId)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _changeWorldById.InvokeFunc(worldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AetheryteTeleportById(uint aetheryteId)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _aetheryteTeleportById.InvokeFunc(aetheryteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanChangeInstance()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _canChangeInstance.InvokeFunc();
|
||||||
|
}
|
||||||
|
public int GetCurrentInstance()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return -1;
|
||||||
|
return _getCurrentInstance.InvokeFunc();
|
||||||
|
}
|
||||||
|
public int GetNumberOfInstances()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return -1;
|
||||||
|
return _getNumberOfInstances.InvokeFunc();
|
||||||
|
}
|
||||||
|
public void ChangeInstance(int instanceNumber)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return;
|
||||||
|
_changeInstance.InvokeAction(instanceNumber);
|
||||||
|
}
|
||||||
|
public (ResidentialAetheryteKind, int, int)? GetCurrentPlotInfo()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return (ResidentialAetheryteKind.None, -1, -1);
|
||||||
|
return _getCurrentPlotInfo.InvokeFunc();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,8 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
public IpcManager(ILogger<IpcManager> logger, LightlessMediator mediator,
|
public IpcManager(ILogger<IpcManager> logger, LightlessMediator mediator,
|
||||||
IpcCallerPenumbra penumbraIpc, IpcCallerGlamourer glamourerIpc, IpcCallerCustomize customizeIpc, IpcCallerHeels heelsIpc,
|
IpcCallerPenumbra penumbraIpc, IpcCallerGlamourer glamourerIpc, IpcCallerCustomize customizeIpc, IpcCallerHeels heelsIpc,
|
||||||
IpcCallerHonorific honorificIpc, IpcCallerMoodles moodlesIpc, IpcCallerPetNames ipcCallerPetNames, IpcCallerBrio ipcCallerBrio) : base(logger, mediator)
|
IpcCallerHonorific honorificIpc, IpcCallerMoodles moodlesIpc, IpcCallerPetNames ipcCallerPetNames, IpcCallerBrio ipcCallerBrio,
|
||||||
|
IpcCallerLifestream ipcCallerLifestream) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
CustomizePlus = customizeIpc;
|
CustomizePlus = customizeIpc;
|
||||||
Heels = heelsIpc;
|
Heels = heelsIpc;
|
||||||
@@ -17,6 +18,7 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
Moodles = moodlesIpc;
|
Moodles = moodlesIpc;
|
||||||
PetNames = ipcCallerPetNames;
|
PetNames = ipcCallerPetNames;
|
||||||
Brio = ipcCallerBrio;
|
Brio = ipcCallerBrio;
|
||||||
|
Lifestream = ipcCallerLifestream;
|
||||||
|
|
||||||
if (Initialized)
|
if (Initialized)
|
||||||
{
|
{
|
||||||
@@ -44,8 +46,8 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
public IpcCallerPenumbra Penumbra { get; }
|
public IpcCallerPenumbra Penumbra { get; }
|
||||||
public IpcCallerMoodles Moodles { get; }
|
public IpcCallerMoodles Moodles { get; }
|
||||||
public IpcCallerPetNames PetNames { get; }
|
public IpcCallerPetNames PetNames { get; }
|
||||||
|
|
||||||
public IpcCallerBrio Brio { get; }
|
public IpcCallerBrio Brio { get; }
|
||||||
|
public IpcCallerLifestream Lifestream { get; }
|
||||||
|
|
||||||
private void PeriodicApiStateCheck()
|
private void PeriodicApiStateCheck()
|
||||||
{
|
{
|
||||||
@@ -58,5 +60,6 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
Moodles.CheckAPI();
|
Moodles.CheckAPI();
|
||||||
PetNames.CheckAPI();
|
PetNames.CheckAPI();
|
||||||
Brio.CheckAPI();
|
Brio.CheckAPI();
|
||||||
|
Lifestream.CheckAPI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,6 +373,11 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
sp.GetRequiredService<DalamudUtilService>(),
|
sp.GetRequiredService<DalamudUtilService>(),
|
||||||
sp.GetRequiredService<LightlessMediator>()));
|
sp.GetRequiredService<LightlessMediator>()));
|
||||||
|
|
||||||
|
services.AddSingleton(sp => new IpcCallerLifestream(
|
||||||
|
pluginInterface,
|
||||||
|
sp.GetRequiredService<LightlessMediator>(),
|
||||||
|
sp.GetRequiredService<ILogger<IpcCallerLifestream>>()));
|
||||||
|
|
||||||
services.AddSingleton(sp => new IpcManager(
|
services.AddSingleton(sp => new IpcManager(
|
||||||
sp.GetRequiredService<ILogger<IpcManager>>(),
|
sp.GetRequiredService<ILogger<IpcManager>>(),
|
||||||
sp.GetRequiredService<LightlessMediator>(),
|
sp.GetRequiredService<LightlessMediator>(),
|
||||||
@@ -383,7 +388,9 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
sp.GetRequiredService<IpcCallerHonorific>(),
|
sp.GetRequiredService<IpcCallerHonorific>(),
|
||||||
sp.GetRequiredService<IpcCallerMoodles>(),
|
sp.GetRequiredService<IpcCallerMoodles>(),
|
||||||
sp.GetRequiredService<IpcCallerPetNames>(),
|
sp.GetRequiredService<IpcCallerPetNames>(),
|
||||||
sp.GetRequiredService<IpcCallerBrio>()));
|
sp.GetRequiredService<IpcCallerBrio>(),
|
||||||
|
sp.GetRequiredService<IpcCallerLifestream>()
|
||||||
|
));
|
||||||
|
|
||||||
// Notifications / HTTP
|
// Notifications / HTTP
|
||||||
services.AddSingleton(sp => new NotificationService(
|
services.AddSingleton(sp => new NotificationService(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Dalamud.Interface.Colors;
|
|||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
using Lifestream.Enums;
|
||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Data.Comparer;
|
using LightlessSync.API.Data.Comparer;
|
||||||
using LightlessSync.API.Data.Enum;
|
using LightlessSync.API.Data.Enum;
|
||||||
@@ -1243,7 +1244,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard"))
|
||||||
{
|
{
|
||||||
if (LastCreatedCharacterData != null)
|
if (LastCreatedCharacterData != null)
|
||||||
@@ -1259,6 +1259,39 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server.");
|
UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server.");
|
||||||
|
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Home, "Teleport to Limsa [LIFESTREAM TEST]") && _ipcManager.Lifestream.APIAvailable)
|
||||||
|
{
|
||||||
|
_ipcManager.Lifestream.ExecuteLifestreamCommand("limsa");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Home, "Teleport to JoyHouse [LIFESTREAM TEST]") && _ipcManager.Lifestream.APIAvailable)
|
||||||
|
{
|
||||||
|
var twintania = _dalamudUtilService.WorldData.Value
|
||||||
|
.FirstOrDefault(kvp => kvp.Value.Equals("Twintania", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
int ward = 29;
|
||||||
|
int plot = 7;
|
||||||
|
|
||||||
|
AddressBookEntryTuple addressEntry = (
|
||||||
|
Name: "",
|
||||||
|
World: (int)twintania.Key,
|
||||||
|
City: (int)ResidentialAetheryteKind.Kugane,
|
||||||
|
Ward: ward,
|
||||||
|
PropertyType: 0,
|
||||||
|
Plot: plot,
|
||||||
|
Apartment: 1,
|
||||||
|
ApartmentSubdivision: false,
|
||||||
|
AliasEnabled: false,
|
||||||
|
Alias: ""
|
||||||
|
);
|
||||||
|
|
||||||
|
_logger.LogInformation("going to: {address}", addressEntry);
|
||||||
|
|
||||||
|
_ipcManager.Lifestream.GoToHousingAddress(addressEntry);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
_uiShared.DrawCombo("Log Level", Enum.GetValues<LogLevel>(), (l) => l.ToString(), (l) =>
|
_uiShared.DrawCombo("Log Level", Enum.GetValues<LogLevel>(), (l) => l.ToString(), (l) =>
|
||||||
{
|
{
|
||||||
_configService.Current.LogLevel = l;
|
_configService.Current.LogLevel = l;
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
private readonly Dictionary<string, DateTime> _oauthTokenExpiry = [];
|
private readonly Dictionary<string, DateTime> _oauthTokenExpiry = [];
|
||||||
private bool _penumbraExists = false;
|
private bool _penumbraExists = false;
|
||||||
private bool _petNamesExists = false;
|
private bool _petNamesExists = false;
|
||||||
|
private bool _lifestreamExists = false;
|
||||||
private int _serverSelectionIndex = -1;
|
private int _serverSelectionIndex = -1;
|
||||||
public UiSharedService(ILogger<UiSharedService> logger, IpcManager ipcManager, ApiController apiController,
|
public UiSharedService(ILogger<UiSharedService> logger, IpcManager ipcManager, ApiController apiController,
|
||||||
CacheMonitor cacheMonitor, FileDialogManager fileDialogManager,
|
CacheMonitor cacheMonitor, FileDialogManager fileDialogManager,
|
||||||
@@ -112,6 +113,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
_moodlesExists = _ipcManager.Moodles.APIAvailable;
|
_moodlesExists = _ipcManager.Moodles.APIAvailable;
|
||||||
_petNamesExists = _ipcManager.PetNames.APIAvailable;
|
_petNamesExists = _ipcManager.PetNames.APIAvailable;
|
||||||
_brioExists = _ipcManager.Brio.APIAvailable;
|
_brioExists = _ipcManager.Brio.APIAvailable;
|
||||||
|
_lifestreamExists = _ipcManager.Lifestream.APIAvailable;
|
||||||
});
|
});
|
||||||
|
|
||||||
UidFont = _pluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
UidFont = _pluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
||||||
@@ -1105,6 +1107,10 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ColorText("Brio", GetBoolColor(_brioExists));
|
ColorText("Brio", GetBoolColor(_brioExists));
|
||||||
AttachToolTip(BuildPluginTooltip("Brio", _brioExists, _ipcManager.Brio.State));
|
AttachToolTip(BuildPluginTooltip("Brio", _brioExists, _ipcManager.Brio.State));
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ColorText("Lifestream", GetBoolColor(_lifestreamExists));
|
||||||
|
AttachToolTip(BuildPluginTooltip("Lifestream", _lifestreamExists, _ipcManager.Lifestream.State));
|
||||||
|
|
||||||
if (!_penumbraExists || !_glamourerExists)
|
if (!_penumbraExists || !_glamourerExists)
|
||||||
{
|
{
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Lightless Sync.");
|
ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Lightless Sync.");
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
private readonly SemaphoreSlim _decompressGate =
|
private readonly SemaphoreSlim _decompressGate =
|
||||||
new(Math.Max(1, Environment.ProcessorCount / 2), Math.Max(1, Environment.ProcessorCount / 2));
|
new(Math.Max(1, Environment.ProcessorCount / 2), Math.Max(1, Environment.ProcessorCount / 2));
|
||||||
|
|
||||||
private readonly ConcurrentQueue<string> _deferredCompressionQueue = new();
|
|
||||||
|
|
||||||
private volatile bool _disableDirectDownloads;
|
private volatile bool _disableDirectDownloads;
|
||||||
private int _consecutiveDirectDownloadFailures;
|
private int _consecutiveDirectDownloadFailures;
|
||||||
private bool _lastConfigDirectDownloadsState;
|
private bool _lastConfigDirectDownloadsState;
|
||||||
@@ -558,7 +556,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
{
|
{
|
||||||
await File.WriteAllBytesAsync(filePath, Array.Empty<byte>(), ct).ConfigureAwait(false);
|
await _fileCompactor.WriteAllBytesAsync(filePath, Array.Empty<byte>(), ct).ConfigureAwait(false);
|
||||||
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -569,21 +567,17 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
await _decompressGate.WaitAsync(ct).ConfigureAwait(false);
|
await _decompressGate.WaitAsync(ct).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// offload CPU-intensive decompression to threadpool to free up worker
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
await Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
|
||||||
|
|
||||||
// decompress
|
// decompress
|
||||||
var decompressed = LZ4Wrapper.Unwrap(compressed);
|
var decompressed = LZ4Wrapper.Unwrap(compressed);
|
||||||
|
|
||||||
Logger.LogTrace("{dlName}: Unwrap {fileHash} took {ms}ms (compressed {c} bytes, decompressed {d} bytes)",
|
Logger.LogTrace("{dlName}: Unwrap {fileHash} took {ms}ms (compressed {c} bytes, decompressed {d} bytes)",
|
||||||
downloadLabel, fileHash, sw.ElapsedMilliseconds, compressed.Length, decompressed?.Length ?? -1);
|
downloadLabel, fileHash, sw.ElapsedMilliseconds, compressed.Length, decompressed?.Length ?? -1);
|
||||||
|
|
||||||
// write to file without compacting during download
|
// write to file
|
||||||
await File.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false);
|
await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false);
|
||||||
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
||||||
}, ct).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -758,16 +752,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
if (gameObjectHandler is not null)
|
if (gameObjectHandler is not null)
|
||||||
Mediator.Publish(new DownloadStartedMessage(gameObjectHandler, _downloadStatus));
|
Mediator.Publish(new DownloadStartedMessage(gameObjectHandler, _downloadStatus));
|
||||||
|
|
||||||
// work based on cpu count and slots
|
|
||||||
var coreCount = Environment.ProcessorCount;
|
|
||||||
var baseWorkers = Math.Min(slots, coreCount);
|
|
||||||
|
|
||||||
// only add buffer if decompression has capacity AND we have cores to spare
|
|
||||||
var availableDecompressSlots = _decompressGate.CurrentCount;
|
|
||||||
var extraWorkers = (availableDecompressSlots > 0 && coreCount >= 6) ? 2 : 0;
|
|
||||||
|
|
||||||
// allow some extra workers so downloads can continue while earlier items decompress.
|
// allow some extra workers so downloads can continue while earlier items decompress.
|
||||||
var workerDop = Math.Clamp(baseWorkers + extraWorkers, 2, coreCount);
|
var workerDop = Math.Clamp(slots * 2, 2, 16);
|
||||||
|
|
||||||
// batch downloads
|
// batch downloads
|
||||||
Task batchTask = batchChunks.Length == 0
|
Task batchTask = batchChunks.Length == 0
|
||||||
@@ -783,9 +769,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
await Task.WhenAll(batchTask, directTask).ConfigureAwait(false);
|
await Task.WhenAll(batchTask, directTask).ConfigureAwait(false);
|
||||||
|
|
||||||
// process deferred compressions after all downloads complete
|
|
||||||
await ProcessDeferredCompressionsAsync(ct).ConfigureAwait(false);
|
|
||||||
|
|
||||||
Logger.LogDebug("Download end: {id}", objectName);
|
Logger.LogDebug("Download end: {id}", objectName);
|
||||||
ClearDownload();
|
ClearDownload();
|
||||||
}
|
}
|
||||||
@@ -890,7 +873,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
byte[] compressedBytes = await File.ReadAllBytesAsync(tempFilename, ct).ConfigureAwait(false);
|
byte[] compressedBytes = await File.ReadAllBytesAsync(tempFilename, ct).ConfigureAwait(false);
|
||||||
var decompressedBytes = LZ4Wrapper.Unwrap(compressedBytes);
|
var decompressedBytes = LZ4Wrapper.Unwrap(compressedBytes);
|
||||||
|
|
||||||
await File.WriteAllBytesAsync(finalFilename, decompressedBytes, ct).ConfigureAwait(false);
|
await _fileCompactor.WriteAllBytesAsync(finalFilename, decompressedBytes, ct).ConfigureAwait(false);
|
||||||
PersistFileToStorage(directDownload.Hash, finalFilename, repl.GamePath, skipDownscale);
|
PersistFileToStorage(directDownload.Hash, finalFilename, repl.GamePath, skipDownscale);
|
||||||
|
|
||||||
MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1);
|
MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1);
|
||||||
@@ -1018,10 +1001,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
fi.LastAccessTime = DateTime.Today;
|
fi.LastAccessTime = DateTime.Today;
|
||||||
fi.LastWriteTime = RandomDayInThePast().Invoke();
|
fi.LastWriteTime = RandomDayInThePast().Invoke();
|
||||||
|
|
||||||
// queue file for deferred compression instead of compressing immediately
|
|
||||||
if (_configService.Current.UseCompactor)
|
|
||||||
_deferredCompressionQueue.Enqueue(filePath);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var entry = _fileDbManager.CreateCacheEntry(filePath);
|
var entry = _fileDbManager.CreateCacheEntry(filePath);
|
||||||
@@ -1047,52 +1026,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private static IProgress<long> CreateInlineProgress(Action<long> callback) => new InlineProgress(callback);
|
private static IProgress<long> CreateInlineProgress(Action<long> callback) => new InlineProgress(callback);
|
||||||
|
|
||||||
private async Task ProcessDeferredCompressionsAsync(CancellationToken ct)
|
|
||||||
{
|
|
||||||
if (_deferredCompressionQueue.IsEmpty)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var filesToCompress = new List<string>();
|
|
||||||
while (_deferredCompressionQueue.TryDequeue(out var filePath))
|
|
||||||
{
|
|
||||||
if (File.Exists(filePath))
|
|
||||||
filesToCompress.Add(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filesToCompress.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Logger.LogDebug("Starting deferred compression of {count} files", filesToCompress.Count);
|
|
||||||
|
|
||||||
var compressionWorkers = Math.Clamp(Environment.ProcessorCount / 4, 2, 4);
|
|
||||||
|
|
||||||
await Parallel.ForEachAsync(filesToCompress,
|
|
||||||
new ParallelOptions
|
|
||||||
{
|
|
||||||
MaxDegreeOfParallelism = compressionWorkers,
|
|
||||||
CancellationToken = ct
|
|
||||||
},
|
|
||||||
async (filePath, token) =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Yield();
|
|
||||||
if (_configService.Current.UseCompactor && File.Exists(filePath))
|
|
||||||
{
|
|
||||||
var bytes = await File.ReadAllBytesAsync(filePath, token).ConfigureAwait(false);
|
|
||||||
await _fileCompactor.WriteAllBytesAsync(filePath, bytes, token).ConfigureAwait(false);
|
|
||||||
Logger.LogTrace("Compressed file: {filePath}", filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogWarning(ex, "Failed to compress file: {filePath}", filePath);
|
|
||||||
}
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
|
|
||||||
Logger.LogDebug("Completed deferred compression of {count} files", filesToCompress.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class InlineProgress : IProgress<long>
|
private sealed class InlineProgress : IProgress<long>
|
||||||
{
|
{
|
||||||
private readonly Action<long> _callback;
|
private readonly Action<long> _callback;
|
||||||
|
|||||||
@@ -76,6 +76,19 @@
|
|||||||
"Microsoft.AspNetCore.SignalR.Common": "10.0.1"
|
"Microsoft.AspNetCore.SignalR.Common": "10.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.Caching.Memory": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[10.0.1, )",
|
||||||
|
"resolved": "10.0.1",
|
||||||
|
"contentHash": "NxqSP0Ky4dZ5ybszdZCqs1X2C70s+dXflqhYBUh/vhcQVTIooNCXIYnLVbafoAFGZMs51d9+rHxveXs0ZC3SQQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Caching.Abstractions": "10.0.1",
|
||||||
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
|
||||||
|
"Microsoft.Extensions.Options": "10.0.1",
|
||||||
|
"Microsoft.Extensions.Primitives": "10.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.Extensions.Hosting": {
|
"Microsoft.Extensions.Hosting": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.0.1, )",
|
"requested": "[10.0.1, )",
|
||||||
@@ -233,6 +246,14 @@
|
|||||||
"Microsoft.AspNetCore.SignalR.Common": "10.0.1"
|
"Microsoft.AspNetCore.SignalR.Common": "10.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.Caching.Abstractions": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "10.0.1",
|
||||||
|
"contentHash": "Vb1vVAQDxHpXVdL9fpOX2BzeV7bbhzG4pAcIKRauRl0/VfkE8mq0f+fYC+gWICh3dlzTZInJ/cTeBS2MgU/XvQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Primitives": "10.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.Extensions.Configuration": {
|
"Microsoft.Extensions.Configuration": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "10.0.1",
|
"resolved": "10.0.1",
|
||||||
@@ -618,7 +639,7 @@
|
|||||||
"FlatSharp.Compiler": "[7.9.0, )",
|
"FlatSharp.Compiler": "[7.9.0, )",
|
||||||
"FlatSharp.Runtime": "[7.9.0, )",
|
"FlatSharp.Runtime": "[7.9.0, )",
|
||||||
"OtterGui": "[1.0.0, )",
|
"OtterGui": "[1.0.0, )",
|
||||||
"Penumbra.Api": "[5.13.0, )",
|
"Penumbra.Api": "[5.13.1, )",
|
||||||
"Penumbra.String": "[1.0.7, )"
|
"Penumbra.String": "[1.0.7, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user