add UI for Mare Data Analysis

This commit is contained in:
rootdarkarchon
2023-07-12 15:03:15 +02:00
parent 405c0968da
commit 42da1855b1
12 changed files with 474 additions and 98 deletions

View File

@@ -4,18 +4,22 @@ namespace MareSynchronos.FileCache;
public class FileCacheEntity
{
public FileCacheEntity(string hash, string path, string lastModifiedDateTicks)
public FileCacheEntity(string hash, string path, string lastModifiedDateTicks, long? size = null, long? compressedSize = null)
{
Size = size;
CompressedSize = compressedSize;
Hash = hash;
PrefixedFilePath = path;
LastModifiedDateTicks = lastModifiedDateTicks;
}
public string CsvEntry => $"{Hash}{FileCacheManager.CsvSplit}{PrefixedFilePath}{FileCacheManager.CsvSplit}{LastModifiedDateTicks}";
public string CsvEntry => $"{Hash}{FileCacheManager.CsvSplit}{PrefixedFilePath}{FileCacheManager.CsvSplit}{LastModifiedDateTicks}|{Size ?? -1}|{CompressedSize ?? -1}";
public string Hash { get; set; }
public string LastModifiedDateTicks { get; set; }
public string PrefixedFilePath { get; init; }
public string ResolvedFilepath { get; private set; } = string.Empty;
public long? Size { get; set; }
public long? CompressedSize { get; set; }
public void SetResolvedFilePath(string filePath)
{

View File

@@ -64,7 +64,20 @@ public sealed class FileCacheManager : IDisposable
if (hash.Length != 40) throw new InvalidOperationException("Expected Hash length of 40, received " + hash.Length);
var path = splittedEntry[1];
var time = splittedEntry[2];
AddHashedFile(ReplacePathPrefixes(new FileCacheEntity(hash, path, time)));
long size = -1;
long compressed = -1;
if (splittedEntry.Length > 3)
{
if (long.TryParse(splittedEntry[3], CultureInfo.InvariantCulture, out long result))
{
size = result;
}
if (long.TryParse(splittedEntry[4], CultureInfo.InvariantCulture, out long resultCompressed))
{
compressed = resultCompressed;
}
}
AddHashedFile(ReplacePathPrefixes(new FileCacheEntity(hash, path, time, size, compressed)));
}
catch (Exception ex)
{
@@ -122,6 +135,21 @@ public sealed class FileCacheManager : IDisposable
return null;
}
public List<FileCacheEntity> GetAllFileCachesByHash(string hash)
{
List<FileCacheEntity> output = new();
if (_fileCaches.TryGetValue(hash, out var fileCacheEntities))
{
foreach (var filecache in fileCacheEntities)
{
var validated = GetValidatedFileCache(filecache);
if (validated != null) output.Add(validated);
}
}
return output;
}
public FileCacheEntity? GetFileCacheByPath(string path)
{
var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase);
@@ -158,11 +186,17 @@ public sealed class FileCacheManager : IDisposable
}
}
public void UpdateHashedFile(FileCacheEntity fileCache)
public void UpdateHashedFile(FileCacheEntity fileCache, bool computeProperties = true)
{
_logger.LogTrace("Updating hash for {path}", fileCache.ResolvedFilepath);
if (computeProperties)
{
var fi = new FileInfo(fileCache.ResolvedFilepath);
fileCache.Size = fi.Length;
fileCache.CompressedSize = null;
fileCache.Hash = Crypto.GetFileHash(fileCache.ResolvedFilepath);
fileCache.LastModifiedDateTicks = new FileInfo(fileCache.ResolvedFilepath).LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture);
fileCache.LastModifiedDateTicks = fi.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture);
}
RemoveHashedFile(fileCache);
AddHashedFile(fileCache);
}
@@ -247,7 +281,7 @@ public sealed class FileCacheManager : IDisposable
private FileCacheEntity? CreateFileCacheEntity(FileInfo fileInfo, string prefixedPath, string? hash = null)
{
hash ??= Crypto.GetFileHash(fileInfo.FullName);
var entity = new FileCacheEntity(hash, prefixedPath, fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture));
var entity = new FileCacheEntity(hash, prefixedPath, fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture), fileInfo.Length);
entity = ReplacePathPrefixes(entity);
AddHashedFile(entity);
lock (_fileWriteLock)

View File

@@ -87,6 +87,10 @@
<HintPath>$(DalamudLibPath)CheapLoc.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Dalamud.Interface">
<HintPath>$(DalamudLibPath)Dalamud.Interface.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<ItemGroup>

View File

@@ -106,6 +106,7 @@ public sealed class Plugin : IDalamudPlugin
collection.AddScoped<WindowMediatorSubscriberBase, IntroUi>();
collection.AddScoped<WindowMediatorSubscriberBase, DownloadUi>();
collection.AddScoped<WindowMediatorSubscriberBase, PopoutProfileUi>();
collection.AddScoped<WindowMediatorSubscriberBase, DataAnalysisUi>();
collection.AddScoped<WindowMediatorSubscriberBase, EditProfileUi>((s) => new EditProfileUi(s.GetRequiredService<ILogger<EditProfileUi>>(),
s.GetRequiredService<MareMediator>(), s.GetRequiredService<ApiController>(), pluginInterface.UiBuilder, s.GetRequiredService<UiSharedService>(),
s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<MareProfileManager>()));
@@ -118,7 +119,7 @@ public sealed class Plugin : IDalamudPlugin
s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<MareMediator>()));
collection.AddScoped((s) => new CommandManagerService(commandManager, s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<UiService>(),
s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<PeriodicFileScanner>(), s.GetRequiredService<ApiController>(),
s.GetRequiredService<MareMediator>(), s.GetRequiredService<CharacterAnalyzer>()));
s.GetRequiredService<MareMediator>()));
collection.AddScoped((s) => new NotificationService(s.GetRequiredService<ILogger<NotificationService>>(),
s.GetRequiredService<MareMediator>(), pluginInterface.UiBuilder, chatGui, s.GetRequiredService<MareConfigService>()));
collection.AddScoped((s) => new UiSharedService(s.GetRequiredService<ILogger<UiSharedService>>(), s.GetRequiredService<IpcManager>(), s.GetRequiredService<ApiController>(),

View File

@@ -1,4 +1,5 @@
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.FileCache;
using MareSynchronos.Services.Mediator;
using MareSynchronos.UI;
@@ -10,82 +11,146 @@ namespace MareSynchronos.Services;
public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
{
private readonly FileCacheManager _fileCacheManager;
private CharacterData? _lastCreatedData;
private CancellationTokenSource? _analysisCts;
private string _lastDataHash = string.Empty;
internal Dictionary<ObjectKind, Dictionary<string, FileDataEntry>> LastAnalysis { get; } = new();
public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, MareMediator mediator, FileCacheManager fileCacheManager) : base(logger, mediator)
{
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) =>
{
_lastCreatedData = msg.CharacterData.DeepClone();
_ = Task.Run(() => BaseAnalysis(msg.CharacterData.DeepClone()));
});
_fileCacheManager = fileCacheManager;
}
public bool IsAnalysisRunning => _analysisCts != null;
public int CurrentFile { get; internal set; }
public int TotalFiles { get; internal set; }
public void CancelAnalyze()
{
_analysisCts?.CancelDispose();
_analysisCts = null;
}
public async Task Analyze()
public async Task ComputeAnalysis(bool print = true)
{
Logger.LogDebug("=== Calculating Character Analysis ===");
_analysisCts = _analysisCts?.CancelRecreate() ?? new();
var cancelToken = _analysisCts.Token;
if (_lastCreatedData == null) return;
Logger.LogInformation("=== Calculating Character Analysis, this may take a while ===");
foreach (var obj in _lastCreatedData.FileReplacements)
var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList();
if (allFiles.Exists(c => !c.IsComputed))
{
Logger.LogInformation("=== File Calculation for {obj} ===", obj.Key);
Dictionary<string, List<DataEntry>> data = new(StringComparer.OrdinalIgnoreCase);
var totalFiles = obj.Value.Count(c => !string.IsNullOrEmpty(c.Hash));
var currentFile = 1;
foreach (var hash in obj.Value.Select(c => c.Hash))
var remaining = allFiles.Where(c => !c.IsComputed).ToList();
TotalFiles = remaining.Count;
CurrentFile = 1;
Logger.LogDebug("=== Computing {amount} remaining files ===", remaining.Count);
Mediator.Publish(new HaltScanMessage("CharacterAnalyzer"));
foreach (var file in remaining)
{
var fileCacheEntry = _fileCacheManager.GetFileCacheByHash(hash);
if (fileCacheEntry == null) continue;
Logger.LogInformation("Computing File {x}/{y}: {hash}", currentFile, totalFiles, hash);
Logger.LogInformation(" File Path: {path}", fileCacheEntry.ResolvedFilepath);
var filePath = fileCacheEntry.ResolvedFilepath;
FileInfo fi = new(filePath);
var ext = fi.Extension;
if (!data.ContainsKey(ext)) data[ext] = new List<DataEntry>();
(_, byte[] fileLength) = await _fileCacheManager.GetCompressedFileData(hash, cancelToken).ConfigureAwait(false);
Logger.LogInformation(" Original Size: {size}, Compressed Size: {compr}",
UiSharedService.ByteToString(fi.Length), UiSharedService.ByteToString(fileLength.LongLength));
data[ext].Add(new DataEntry(fi.FullName, fi.Length, fileLength.LongLength));
currentFile++;
cancelToken.ThrowIfCancellationRequested();
await file.ComputeSizes(_fileCacheManager, cancelToken).ConfigureAwait(false);
CurrentFile++;
}
Logger.LogInformation("=== Summary by file type for {obj} ===", obj.Key);
foreach (var entry in data)
{
Logger.LogInformation("{ext} files: {count}, size extracted: {size}, size compressed: {sizeComp}", entry.Key, entry.Value.Count,
UiSharedService.ByteToString(entry.Value.Sum(v => v.OriginalSize)), UiSharedService.ByteToString(entry.Value.Sum(v => v.CompressedSize)));
}
Logger.LogInformation("=== Total summary for {obj} ===", obj.Key);
Logger.LogInformation("Total files: {count}, size extracted: {size}, size compressed: {sizeComp}", data.Values.Sum(c => c.Count),
UiSharedService.ByteToString(data.Values.Sum(v => v.Sum(c => c.OriginalSize))), UiSharedService.ByteToString(data.Values.Sum(v => v.Sum(c => c.CompressedSize))));
_fileCacheManager.WriteOutFullCsv();
Logger.LogInformation("IMPORTANT NOTES:\n\r- For Mare up- and downloads only the compressed size is relevant.\n\r- An unusually high total files count beyond 200 and up will also increase your download time to others significantly.");
Mediator.Publish(new ResumeScanMessage("CharacterAnalzyer"));
}
Mediator.Publish(new CharacterDataAnalyzedMessage());
_analysisCts.CancelDispose();
_analysisCts = null;
if (print) PrintAnalysis();
}
private void BaseAnalysis(CharacterData charaData)
{
if (string.Equals(charaData.DataHash.Value, _lastDataHash, StringComparison.Ordinal)) return;
LastAnalysis.Clear();
foreach (var obj in charaData.FileReplacements)
{
Dictionary<string, FileDataEntry> data = new(StringComparer.OrdinalIgnoreCase);
foreach (var fileEntry in obj.Value)
{
var fileCacheEntries = _fileCacheManager.GetAllFileCachesByHash(fileEntry.Hash);
if (fileCacheEntries.Count == 0) continue;
var filePath = fileCacheEntries[0].ResolvedFilepath;
FileInfo fi = new(filePath);
var ext = fi.Extension[1..];
foreach (var entry in fileCacheEntries)
{
data[fileEntry.Hash] = new FileDataEntry(fileEntry.Hash, ext,
fileEntry.GamePaths.ToList(),
fileCacheEntries.Select(c => c.ResolvedFilepath).ToList(),
entry.Size > 0 ? entry.Size.Value : 0, entry.CompressedSize > 0 ? entry.CompressedSize.Value : 0);
}
}
LastAnalysis[obj.Key] = data;
}
Mediator.Publish(new CharacterDataAnalyzedMessage());
_lastDataHash = charaData.DataHash.Value;
}
private void PrintAnalysis()
{
if (LastAnalysis.Count == 0) return;
foreach (var kvp in LastAnalysis)
{
int fileCounter = 1;
int totalFiles = kvp.Value.Count;
Logger.LogInformation("=== Analysis for {obj} ===", kvp.Key);
foreach (var entry in kvp.Value.OrderBy(b => b.Value.GamePaths.OrderBy(p => p, StringComparer.Ordinal).First(), StringComparer.Ordinal))
{
Logger.LogInformation("File {x}/{y}: {hash}", fileCounter++, totalFiles, entry.Key);
foreach (var path in entry.Value.GamePaths)
{
Logger.LogInformation(" Game Path: {path}", path);
}
if (entry.Value.FilePaths.Count > 1) Logger.LogInformation(" Multiple fitting files detected", entry.Key);
foreach (var filePath in entry.Value.FilePaths)
{
Logger.LogInformation(" File Path: {path}", filePath);
}
Logger.LogInformation(" Size: {size}, Compressed: {compressed}", UiSharedService.ByteToString(entry.Value.OriginalSize),
UiSharedService.ByteToString(entry.Value.CompressedSize));
}
}
foreach (var kvp in LastAnalysis)
{
Logger.LogInformation("=== Detailed summary by file type for {obj} ===", kvp.Key);
foreach (var entry in kvp.Value.Select(v => v.Value).GroupBy(v => v.FileType, StringComparer.Ordinal))
{
Logger.LogInformation("{ext} files: {count}, size extracted: {size}, size compressed: {sizeComp}", entry.Key, entry.Count(),
UiSharedService.ByteToString(entry.Sum(v => v.OriginalSize)), UiSharedService.ByteToString(entry.Sum(v => v.CompressedSize)));
}
Logger.LogInformation("=== Total summary for {obj} ===", kvp.Key);
Logger.LogInformation("Total files: {count}, size extracted: {size}, size compressed: {sizeComp}", kvp.Value.Count,
UiSharedService.ByteToString(kvp.Value.Sum(v => v.Value.OriginalSize)), UiSharedService.ByteToString(kvp.Value.Sum(v => v.Value.CompressedSize)));
}
Logger.LogInformation("=== Total summary for all currently present objects ===");
Logger.LogInformation("Total files: {count}, size extracted: {size}, size compressed: {sizeComp}",
LastAnalysis.Values.Sum(v => v.Values.Count),
UiSharedService.ByteToString(LastAnalysis.Values.Sum(c => c.Values.Sum(v => v.OriginalSize))),
UiSharedService.ByteToString(LastAnalysis.Values.Sum(c => c.Values.Sum(v => v.CompressedSize))));
Logger.LogInformation("IMPORTANT NOTES:\n\r- For Mare up- and downloads only the compressed size is relevant.\n\r- An unusually high total files count beyond 200 and up will also increase your download time to others significantly.");
}
public void Dispose()
@@ -93,5 +158,23 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
_analysisCts.CancelDispose();
}
private sealed record DataEntry(string filePath, long OriginalSize, long CompressedSize);
internal sealed record FileDataEntry(string Hash, string FileType, List<string> GamePaths, List<string> FilePaths, long OriginalSize, long CompressedSize)
{
public bool IsComputed => OriginalSize > 0 && CompressedSize > 0;
public async Task ComputeSizes(FileCacheManager fileCacheManager, CancellationToken token)
{
var compressedsize = await fileCacheManager.GetCompressedFileData(Hash, token).ConfigureAwait(false);
var normalSize = new FileInfo(FilePaths[0]).Length;
var entries = fileCacheManager.GetAllFileCachesByHash(Hash);
foreach (var entry in entries)
{
entry.Size = normalSize;
entry.CompressedSize = compressedsize.Item2.LongLength;
}
OriginalSize = normalSize;
CompressedSize = compressedsize.Item2.LongLength;
}
public long OriginalSize { get; private set; } = OriginalSize;
public long CompressedSize { get; private set; } = CompressedSize;
}
}

View File

@@ -14,7 +14,6 @@ public sealed class CommandManagerService : IDisposable
private readonly ApiController _apiController;
private readonly CommandManager _commandManager;
private readonly MareMediator _mediator;
private readonly CharacterAnalyzer _characterAnalyzer;
private readonly PerformanceCollectorService _performanceCollectorService;
private readonly PeriodicFileScanner _periodicFileScanner;
private readonly ServerConfigurationManager _serverConfigurationManager;
@@ -22,7 +21,7 @@ public sealed class CommandManagerService : IDisposable
public CommandManagerService(CommandManager commandManager, PerformanceCollectorService performanceCollectorService,
UiService uiService, ServerConfigurationManager serverConfigurationManager, PeriodicFileScanner periodicFileScanner,
ApiController apiController, MareMediator mediator, CharacterAnalyzer characterAnalyzer)
ApiController apiController, MareMediator mediator)
{
_commandManager = commandManager;
_performanceCollectorService = performanceCollectorService;
@@ -31,7 +30,6 @@ public sealed class CommandManagerService : IDisposable
_periodicFileScanner = periodicFileScanner;
_apiController = apiController;
_mediator = mediator;
_characterAnalyzer = characterAnalyzer;
_commandManager.AddHandler(_commandName, new CommandInfo(OnCommand)
{
HelpMessage = "Opens the Mare Synchronos UI"
@@ -102,14 +100,7 @@ public sealed class CommandManagerService : IDisposable
}
else if (string.Equals(splitArgs[0], "analyze", StringComparison.OrdinalIgnoreCase))
{
if (splitArgs.Length > 1 && string.Equals(splitArgs[1], "cancel", StringComparison.OrdinalIgnoreCase))
{
_characterAnalyzer.CancelAnalyze();
}
else
{
_ = _characterAnalyzer.Analyze();
}
_mediator.Publish(new OpenDataAnalysisUiMessage());
}
}
}

View File

@@ -13,6 +13,7 @@ namespace MareSynchronos.Services.Mediator;
public record SwitchToIntroUiMessage : MessageBase;
public record SwitchToMainUiMessage : MessageBase;
public record OpenSettingsUiMessage : MessageBase;
public record OpenDataAnalysisUiMessage : MessageBase;
public record DalamudLoginMessage : MessageBase;
public record DalamudLogoutMessage : MessageBase;
public record FrameworkUpdateMessage : SameThreadMessage;
@@ -49,6 +50,7 @@ public record NotificationMessage
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : MessageBase;
public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : MessageBase;
public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage;
public record CharacterDataAnalyzedMessage : MessageBase;
public record PenumbraStartRedrawMessage(IntPtr Address) : MessageBase;
public record PenumbraEndRedrawMessage(IntPtr Address) : MessageBase;
public record HubReconnectingMessage(Exception? Exception) : MessageBase;

View File

@@ -520,6 +520,11 @@ public class CompactUi : WindowMediatorSubscriberBase
ImGui.Text("No downloads in progress");
}
if (UiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Mare Character Data Analysis", WindowContentWidth))
{
Mediator.Publish(new OpenDataAnalysisUiMessage());
}
ImGui.SameLine();
}

View File

@@ -0,0 +1,268 @@
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Raii;
using ImGuiNET;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils;
using Microsoft.Extensions.Logging;
using System.Numerics;
namespace MareSynchronos.UI;
public class DataAnalysisUi : WindowMediatorSubscriberBase
{
private readonly CharacterAnalyzer _characterAnalyzer;
private bool _hasUpdate = false;
private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _cachedAnalysis;
private string _selectedHash = string.Empty;
private ObjectKind _selectedTab;
public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator, CharacterAnalyzer characterAnalyzer) : base(logger, mediator, "Mare Character Data Analysis")
{
_characterAnalyzer = characterAnalyzer;
Mediator.Subscribe<CharacterDataAnalyzedMessage>(this, (_) =>
{
_hasUpdate = true;
});
Mediator.Subscribe<OpenDataAnalysisUiMessage>(this, (_) => Toggle());
SizeConstraints = new()
{
MinimumSize = new()
{
X = 800,
Y = 600
},
MaximumSize = new()
{
X = 3840,
Y = 2160
}
};
}
public override void OnOpen()
{
_hasUpdate = true;
_selectedHash = string.Empty;
}
public override void Draw()
{
if (_hasUpdate)
{
_cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone();
_hasUpdate = false;
}
UiSharedService.TextWrapped("This window shows you all files and their sizes that are currently in use through your character and associated entities in Mare");
if (_cachedAnalysis!.Count == 0) return;
if (_cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed)))
{
bool isAnalyzing = _characterAnalyzer.IsAnalysisRunning;
if (isAnalyzing)
{
UiSharedService.ColorTextWrapped($"Analyzing {_characterAnalyzer.CurrentFile}/{_characterAnalyzer.TotalFiles}",
ImGuiColors.DalamudYellow);
if (UiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis"))
{
_characterAnalyzer.CancelAnalyze();
}
}
else
{
UiSharedService.ColorTextWrapped("Some entries in the analysis have file size not determined yet, press the button below to analyze your current data",
ImGuiColors.DalamudYellow);
if (UiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis"))
{
_ = _characterAnalyzer.ComputeAnalysis(false);
}
}
}
ImGui.Separator();
ImGui.TextUnformatted("Total files:");
ImGui.SameLine();
ImGui.TextUnformatted(_cachedAnalysis!.Values.Sum(c => c.Values.Count).ToString());
ImGui.SameLine();
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
}
if (ImGui.IsItemHovered())
{
string text = "";
var groupedfiles = _cachedAnalysis.Values.SelectMany(f => f.Values).GroupBy(f => f.FileType, StringComparer.Ordinal);
text = string.Join(Environment.NewLine, groupedfiles.OrderBy(f => f.Key, StringComparer.Ordinal)
.Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
ImGui.SetTooltip(text);
}
ImGui.TextUnformatted("Total size (uncompressed):");
ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize))));
ImGui.TextUnformatted("Total size (compressed):");
ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.CompressedSize))));
ImGui.Separator();
using var tabbar = ImRaii.TabBar("objectSelection");
foreach (var kvp in _cachedAnalysis)
{
using var id = ImRaii.PushId(kvp.Key.ToString());
string tabText = kvp.Key.ToString();
if (kvp.Value.Any(f => !f.Value.IsComputed)) tabText += " (!)";
using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString());
if (tab.Success)
{
ImGui.TextUnformatted("Files for " + kvp.Key);
ImGui.SameLine();
ImGui.TextUnformatted(kvp.Value.Count.ToString());
ImGui.SameLine();
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
}
if (ImGui.IsItemHovered())
{
string text = "";
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal);
text = string.Join(Environment.NewLine, groupedfiles.OrderBy(f => f.Key, StringComparer.Ordinal)
.Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
ImGui.SetTooltip(text);
}
ImGui.TextUnformatted($"{kvp.Key} size (uncompressed):");
ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
ImGui.TextUnformatted($"{kvp.Key} size (compressed):");
ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
ImGui.Separator();
if (_selectedTab != kvp.Key)
{
_selectedHash = string.Empty;
_selectedTab = kvp.Key;
}
using var table = ImRaii.Table("Analysis", 6, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
new Vector2(0, 300));
if (!table.Success) continue;
ImGui.TableSetupColumn("Type");
ImGui.TableSetupColumn("Hash");
ImGui.TableSetupColumn("Filepaths");
ImGui.TableSetupColumn("Gamepaths");
ImGui.TableSetupColumn("Original Size");
ImGui.TableSetupColumn("Compressed Size");
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
var sortSpecs = ImGui.TableGetSortSpecs();
if (sortSpecs.SpecsDirty)
{
var idx = sortSpecs.Specs.ColumnIndex;
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.FileType, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.FileType, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Key, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Key, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 2 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.FilePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 2 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.FilePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 3 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.GamePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 3 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.GamePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.OriginalSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.OriginalSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
sortSpecs.SpecsDirty = false;
}
foreach (var item in kvp.Value)
{
using var text = ImRaii.PushColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1), string.Equals(item.Key, _selectedHash));
using var text2 = ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 1, 1, 1), !item.Value.IsComputed);
ImGui.TableNextColumn();
if (!item.Value.IsComputed)
{
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(ImGuiColors.DalamudRed));
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(ImGuiColors.DalamudRed));
}
if (string.Equals(_selectedHash, item.Key, StringComparison.Ordinal))
{
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(ImGuiColors.DalamudYellow));
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(ImGuiColors.DalamudYellow));
}
ImGui.TextUnformatted(item.Value.FileType);
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
ImGui.TableNextColumn();
ImGui.TextUnformatted(item.Key);
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
ImGui.TableNextColumn();
ImGui.TextUnformatted(item.Value.FilePaths.Count.ToString());
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
ImGui.TableNextColumn();
ImGui.TextUnformatted(item.Value.GamePaths.Count.ToString());
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
ImGui.TableNextColumn();
ImGui.TextUnformatted(UiSharedService.ByteToString(item.Value.OriginalSize));
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
ImGui.TableNextColumn();
ImGui.TextUnformatted(UiSharedService.ByteToString(item.Value.CompressedSize));
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
}
}
}
ImGui.Separator();
ImGui.Text("Selected file:");
ImGui.SameLine();
UiSharedService.ColorText(_selectedHash, ImGuiColors.DalamudYellow);
if (_cachedAnalysis[_selectedTab].ContainsKey(_selectedHash))
{
var filePaths = _cachedAnalysis[_selectedTab][_selectedHash].FilePaths;
ImGui.TextUnformatted("Local file path:");
ImGui.SameLine();
UiSharedService.TextWrapped(filePaths[0]);
if (filePaths.Count > 1)
{
ImGui.SameLine();
ImGui.TextUnformatted($"(and {filePaths.Count - 1} more)");
ImGui.SameLine();
UiSharedService.FontText(FontAwesomeIcon.InfoCircle.ToIconString(), UiBuilder.IconFont);
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, filePaths.Skip(1)));
}
var gamepaths = _cachedAnalysis[_selectedTab][_selectedHash].GamePaths;
ImGui.TextUnformatted("Used by game path:");
ImGui.SameLine();
UiSharedService.TextWrapped(gamepaths[0]);
if (gamepaths.Count > 1)
{
ImGui.SameLine();
ImGui.TextUnformatted($"(and {gamepaths.Count - 1} more)");
ImGui.SameLine();
UiSharedService.FontText(FontAwesomeIcon.InfoCircle.ToIconString(), UiBuilder.IconFont);
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1)));
}
}
}
}

View File

@@ -32,7 +32,11 @@ public sealed class DtrEntry : IDisposable, IHostedService
public void Dispose()
{
_entry.Value.Dispose();
if (_entry.IsValueCreated)
{
_logger.LogDebug("Disposing DtrEntry");
Clear();
}
}
public Task StartAsync(CancellationToken cancellationToken)
@@ -51,15 +55,6 @@ public sealed class DtrEntry : IDisposable, IHostedService
catch (OperationCanceledException) { }
finally
{
_logger.LogDebug("Disposing DtrEntry");
if (_entry.IsValueCreated)
{
Clear();
_entry.Value.Remove();
_entry.Value.Dispose();
}
_cancellationTokenSource.Dispose();
}
}

View File

@@ -29,7 +29,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new();
private readonly FileUploadManager _fileTransferManager;
private readonly FileTransferOrchestrator _fileTransferOrchestrator;
private readonly CharacterAnalyzer _characterAnalyzer;
private readonly MareCharaFileManager _mareCharaFileManager;
private readonly PairManager _pairManager;
private readonly PerformanceCollectorService _performanceCollector;
@@ -51,8 +50,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ServerConfigurationManager serverConfigurationManager,
MareMediator mediator, PerformanceCollectorService performanceCollector,
FileUploadManager fileTransferManager,
FileTransferOrchestrator fileTransferOrchestrator,
CharacterAnalyzer characterAnalyzer) : base(logger, mediator, "Mare Synchronos Settings")
FileTransferOrchestrator fileTransferOrchestrator) : base(logger, mediator, "Mare Synchronos Settings")
{
_configService = configService;
_mareCharaFileManager = mareCharaFileManager;
@@ -61,7 +59,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
_performanceCollector = performanceCollector;
_fileTransferManager = fileTransferManager;
_fileTransferOrchestrator = fileTransferOrchestrator;
_characterAnalyzer = characterAnalyzer;
_uiShared = uiShared;
SizeConstraints = new WindowSizeConstraints()
@@ -334,23 +331,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
}
UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server.");
var isAnalyzing = _characterAnalyzer.IsAnalysisRunning;
if (isAnalyzing) ImGui.BeginDisabled();
if (UiSharedService.IconTextButton(FontAwesomeIcon.QuestionCircle, "[DEBUG] Analyze current character composition to /xllog"))
{
_ = _characterAnalyzer.Analyze();
}
UiSharedService.AttachToolTip("This will compute your current \"Mare load\" and print it to the /xllog");
if (isAnalyzing) ImGui.EndDisabled();
ImGui.SameLine();
if (!isAnalyzing) ImGui.BeginDisabled();
if (UiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis"))
{
_characterAnalyzer.CancelAnalyze();
}
UiSharedService.AttachToolTip("Cancels the current analysis of your character composition");
if (!isAnalyzing) ImGui.EndDisabled();
_uiShared.DrawCombo("Log Level", Enum.GetValues<LogLevel>(), (l) => l.ToString(), (l) =>
{
_configService.Current.LogLevel = l;

View File

@@ -313,7 +313,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
return ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
}
public static bool IconTextButton(FontAwesomeIcon icon, string text)
public static bool IconTextButton(FontAwesomeIcon icon, string text, float? width = null)
{
var buttonClicked = false;
@@ -322,9 +322,18 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
var padding = ImGui.GetStyle().FramePadding;
var spacing = ImGui.GetStyle().ItemSpacing;
var buttonSizeX = iconSize.X + textSize.X + padding.X * 2 + spacing.X;
Vector2 buttonSize;
var buttonSizeY = (iconSize.Y > textSize.Y ? iconSize.Y : textSize.Y) + padding.Y * 2;
var buttonSize = new Vector2(buttonSizeX, buttonSizeY);
if (width == null)
{
var buttonSizeX = iconSize.X + textSize.X + padding.X * 2 + spacing.X;
buttonSize = new Vector2(buttonSizeX, buttonSizeY);
}
else
{
buttonSize = new Vector2(width.Value, buttonSizeY);
}
if (ImGui.Button("###" + icon.ToIconString() + text, buttonSize))
{