Compare commits
1 Commits
lifestream
...
compressio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d2a914c84 |
@@ -1,11 +0,0 @@
|
|||||||
namespace Lifestream.Enums;
|
|
||||||
|
|
||||||
public enum ResidentialAetheryteKind
|
|
||||||
{
|
|
||||||
None = -1,
|
|
||||||
Uldah = 9,
|
|
||||||
Gridania = 2,
|
|
||||||
Limsa = 8,
|
|
||||||
Foundation = 70,
|
|
||||||
Kugane = 111,
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
global using AddressBookEntryTuple = (string Name, int World, int City, int Ward, int PropertyType, int Plot, int Apartment, bool ApartmentSubdivision, bool AliasEnabled, string Alias);
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
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,8 +7,7 @@ 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,
|
IpcCallerHonorific honorificIpc, IpcCallerMoodles moodlesIpc, IpcCallerPetNames ipcCallerPetNames, IpcCallerBrio ipcCallerBrio) : base(logger, mediator)
|
||||||
IpcCallerLifestream ipcCallerLifestream) : base(logger, mediator)
|
|
||||||
{
|
{
|
||||||
CustomizePlus = customizeIpc;
|
CustomizePlus = customizeIpc;
|
||||||
Heels = heelsIpc;
|
Heels = heelsIpc;
|
||||||
@@ -18,7 +17,6 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
Moodles = moodlesIpc;
|
Moodles = moodlesIpc;
|
||||||
PetNames = ipcCallerPetNames;
|
PetNames = ipcCallerPetNames;
|
||||||
Brio = ipcCallerBrio;
|
Brio = ipcCallerBrio;
|
||||||
Lifestream = ipcCallerLifestream;
|
|
||||||
|
|
||||||
if (Initialized)
|
if (Initialized)
|
||||||
{
|
{
|
||||||
@@ -46,8 +44,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()
|
||||||
{
|
{
|
||||||
@@ -60,6 +58,5 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
Moodles.CheckAPI();
|
Moodles.CheckAPI();
|
||||||
PetNames.CheckAPI();
|
PetNames.CheckAPI();
|
||||||
Brio.CheckAPI();
|
Brio.CheckAPI();
|
||||||
Lifestream.CheckAPI();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,11 +373,6 @@ 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>(),
|
||||||
@@ -388,9 +383,7 @@ 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,7 +5,6 @@ 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;
|
||||||
@@ -1244,6 +1243,7 @@ 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,39 +1259,6 @@ 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,7 +79,6 @@ 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,
|
||||||
@@ -113,7 +112,6 @@ 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 =>
|
||||||
@@ -1107,10 +1105,6 @@ 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.");
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams;
|
private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams;
|
||||||
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;
|
||||||
@@ -556,7 +558,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
{
|
{
|
||||||
await _fileCompactor.WriteAllBytesAsync(filePath, Array.Empty<byte>(), ct).ConfigureAwait(false);
|
await File.WriteAllBytesAsync(filePath, Array.Empty<byte>(), ct).ConfigureAwait(false);
|
||||||
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -567,17 +569,21 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
await _decompressGate.WaitAsync(ct).ConfigureAwait(false);
|
await _decompressGate.WaitAsync(ct).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
// offload CPU-intensive decompression to threadpool to free up worker
|
||||||
|
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
|
// write to file without compacting during download
|
||||||
await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false);
|
await File.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false);
|
||||||
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
||||||
|
}, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -752,8 +758,16 @@ 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(slots * 2, 2, 16);
|
var workerDop = Math.Clamp(baseWorkers + extraWorkers, 2, coreCount);
|
||||||
|
|
||||||
// batch downloads
|
// batch downloads
|
||||||
Task batchTask = batchChunks.Length == 0
|
Task batchTask = batchChunks.Length == 0
|
||||||
@@ -769,6 +783,9 @@ 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();
|
||||||
}
|
}
|
||||||
@@ -873,7 +890,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 _fileCompactor.WriteAllBytesAsync(finalFilename, decompressedBytes, ct).ConfigureAwait(false);
|
await File.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);
|
||||||
@@ -1001,6 +1018,10 @@ 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);
|
||||||
@@ -1026,6 +1047,52 @@ 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,19 +76,6 @@
|
|||||||
"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, )",
|
||||||
@@ -246,14 +233,6 @@
|
|||||||
"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",
|
||||||
@@ -639,7 +618,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.1, )",
|
"Penumbra.Api": "[5.13.0, )",
|
||||||
"Penumbra.String": "[1.0.7, )"
|
"Penumbra.String": "[1.0.7, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user