From 535c16c304826530f0e645e42beffc85d6a8e8f3 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 16 Mar 2024 14:01:27 +1100 Subject: [PATCH 1/3] Add audio filepath completion helper Due to how audio is packaged server doesn't have most audio files. --- Robust.Shared/Collections/ValueList.cs | 8 +++++++ Robust.Shared/Console/CompletionHelper.cs | 24 ++++++++++++++++++++ Robust.Shared/Console/CompletionResult.cs | 8 ++++++- Robust.Shared/ContentPack/ResourceManager.cs | 6 +++-- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/Collections/ValueList.cs b/Robust.Shared/Collections/ValueList.cs index 6892778ab6c..22b31f2f1f1 100644 --- a/Robust.Shared/Collections/ValueList.cs +++ b/Robust.Shared/Collections/ValueList.cs @@ -607,4 +607,12 @@ public void EnsureLength(int newCount) region.Clear(); Count = newCount; } + + public void AddRange(IEnumerable select) + { + foreach (var result in select) + { + Add(result); + } + } } diff --git a/Robust.Shared/Console/CompletionHelper.cs b/Robust.Shared/Console/CompletionHelper.cs index 440e66e6156..447075d8c36 100644 --- a/Robust.Shared/Console/CompletionHelper.cs +++ b/Robust.Shared/Console/CompletionHelper.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using Robust.Shared.Audio; +using Robust.Shared.Collections; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -23,6 +25,28 @@ public static class CompletionHelper public static IEnumerable Booleans => new[] { new CompletionOption(bool.FalseString), new CompletionOption(bool.TrueString) }; + /// + /// Special-cased file handler for audio that accounts for serverside completion. + /// + public static IEnumerable AudioFilePath(string arg, IPrototypeManager protoManager, + IResourceManager res) + { + var results = new ValueList(); + + foreach (var proto in protoManager.EnumeratePrototypes()) + { + if (!proto.ID.StartsWith(arg)) + continue; + + results.Add(new CompletionOption(proto.ID)); + } + + results.AddRange(ContentFilePath(arg, res)); + results.Sort(); + + return results; + } + public static IEnumerable ContentFilePath(string arg, IResourceManager res) { var curPath = arg; diff --git a/Robust.Shared/Console/CompletionResult.cs b/Robust.Shared/Console/CompletionResult.cs index f3e2c37a13f..64eab32568d 100644 --- a/Robust.Shared/Console/CompletionResult.cs +++ b/Robust.Shared/Console/CompletionResult.cs @@ -38,7 +38,7 @@ private static CompletionOption[] ConvertOptions(IEnumerable stringOpts) /// /// Possible option to tab-complete in a . /// -public record struct CompletionOption(string Value, string? Hint = null, CompletionOptionFlags Flags = default) +public record struct CompletionOption(string Value, string? Hint = null, CompletionOptionFlags Flags = default) : IComparable { /// /// The value that will be filled in if completed. @@ -54,6 +54,12 @@ public record struct CompletionOption(string Value, string? Hint = null, Complet /// Flags that control how this completion is used. /// public CompletionOptionFlags Flags { get; set; } = Flags; + + public int CompareTo(CompletionOption other) + { + var valueComparison = string.Compare(Value, other.Value, StringComparison.CurrentCultureIgnoreCase); + return valueComparison; + } } /// diff --git a/Robust.Shared/ContentPack/ResourceManager.cs b/Robust.Shared/ContentPack/ResourceManager.cs index a6b262beb26..1e52a2f1eec 100644 --- a/Robust.Shared/ContentPack/ResourceManager.cs +++ b/Robust.Shared/ContentPack/ResourceManager.cs @@ -177,14 +177,16 @@ public bool TryContentFileRead(string path, [NotNullWhen(true)] out Stream? file /// public bool TryContentFileRead(ResPath? path, [NotNullWhen(true)] out Stream? fileStream) { + fileStream = null; + if (path == null) { - throw new ArgumentNullException(nameof(path)); + return false; } if (!path.Value.IsRooted) { - throw new ArgumentException($"Path '{path}' must be rooted", nameof(path)); + return false; } #if DEBUG if (!IsPathValid(path.Value)) From fc093ba038f8c3a90ac8fc99f3d8dcc72c6c3820 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 16 Mar 2024 14:43:43 +1100 Subject: [PATCH 2/3] RN --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 96268ac3d9a..c3934c9c9ae 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Add a CompletionHelper for audio filepaths that handles server packaging. ### Bugfixes From 2ed323e59f3392bcc77f2d8566b5cb83340a62cf Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 17 Mar 2024 20:52:24 +1100 Subject: [PATCH 3/3] weh --- Robust.Shared/Console/CompletionHelper.cs | 40 +++++++++---- Robust.Shared/ContentPack/ResourceManager.cs | 6 +- Robust.Shared/Utility/ResPath.cs | 60 ++++++++++++++++++++ 3 files changed, 91 insertions(+), 15 deletions(-) diff --git a/Robust.Shared/Console/CompletionHelper.cs b/Robust.Shared/Console/CompletionHelper.cs index 447075d8c36..ff0b483993b 100644 --- a/Robust.Shared/Console/CompletionHelper.cs +++ b/Robust.Shared/Console/CompletionHelper.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using JetBrains.Annotations; using Robust.Shared.Audio; @@ -31,23 +32,28 @@ public static class CompletionHelper public static IEnumerable AudioFilePath(string arg, IPrototypeManager protoManager, IResourceManager res) { - var results = new ValueList(); + var resPath = GetUpdatedPath(arg); + var paths = new HashSet(); - foreach (var proto in protoManager.EnumeratePrototypes()) + foreach (var path in res.ContentGetDirectoryEntries(resPath)) { - if (!proto.ID.StartsWith(arg)) + paths.Add(path); + } + + foreach (var audioProto in protoManager.EnumeratePrototypes()) + { + var hero = new ResPath(audioProto.ID); + + if (!hero.TryRelativeTo(resPath, out _)) continue; - results.Add(new CompletionOption(proto.ID)); + paths.Add(hero.GetNextSegment(resPath).ToString()); } - results.AddRange(ContentFilePath(arg, res)); - results.Sort(); - - return results; + return GetPaths(resPath, paths, res); } - public static IEnumerable ContentFilePath(string arg, IResourceManager res) + private static ResPath GetUpdatedPath(string arg) { var curPath = arg; if (!curPath.StartsWith("/")) @@ -55,12 +61,18 @@ public static IEnumerable ContentFilePath(string arg, IResourc var resPath = new ResPath(curPath); - if (!curPath.EndsWith("/")){ + if (!curPath.EndsWith("/")) + { resPath /= ".."; resPath = resPath.Clean(); } - var options = res.ContentGetDirectoryEntries(resPath) + return resPath; + } + + private static IEnumerable GetPaths(ResPath resPath, IEnumerable inputs, IResourceManager res) + { + var options = inputs .OrderBy(c => c) .Select(c => { @@ -75,6 +87,12 @@ public static IEnumerable ContentFilePath(string arg, IResourc return options; } + public static IEnumerable ContentFilePath(string arg, IResourceManager res) + { + var resPath = GetUpdatedPath(arg); + return GetPaths(resPath, res.ContentGetDirectoryEntries(resPath), res); + } + public static IEnumerable ContentDirPath(string arg, IResourceManager res) { var curPath = arg; diff --git a/Robust.Shared/ContentPack/ResourceManager.cs b/Robust.Shared/ContentPack/ResourceManager.cs index 1e52a2f1eec..a6b262beb26 100644 --- a/Robust.Shared/ContentPack/ResourceManager.cs +++ b/Robust.Shared/ContentPack/ResourceManager.cs @@ -177,16 +177,14 @@ public bool TryContentFileRead(string path, [NotNullWhen(true)] out Stream? file /// public bool TryContentFileRead(ResPath? path, [NotNullWhen(true)] out Stream? fileStream) { - fileStream = null; - if (path == null) { - return false; + throw new ArgumentNullException(nameof(path)); } if (!path.Value.IsRooted) { - return false; + throw new ArgumentException($"Path '{path}' must be rooted", nameof(path)); } #if DEBUG if (!IsPathValid(path.Value)) diff --git a/Robust.Shared/Utility/ResPath.cs b/Robust.Shared/Utility/ResPath.cs index 3e347a88e75..5b6d3d8e0c8 100644 --- a/Robust.Shared/Utility/ResPath.cs +++ b/Robust.Shared/Utility/ResPath.cs @@ -569,6 +569,66 @@ public static ResPath Clean(this ResPath path) : new ResPath(sb.ToString()); } + /// + /// Gets the segments in common with 2 paths. + /// + public static ResPath GetCommonSegments(this ResPath path, ResPath other) + { + var segmentsA = path.EnumerateSegments(); + var segmentsB = other.EnumerateSegments(); + + var count = Math.Min(segmentsA.Length, segmentsB.Length); + var common = new ValueList(); + + for (var i = 0; i < count; i++) + { + if (segmentsA[i] == segmentsB[i]) + { + common.Add(segmentsA[i]); + continue; + } + + break; + } + + return new ResPath(string.Join(ResPath.Separator, common)); + } + + /// + /// Gets the next segment after where the common segments end. + /// + public static ResPath GetNextSegment(this ResPath path, ResPath other) + { + var segmentsA = path.EnumerateSegments(); + var segmentsB = other.EnumerateSegments(); + + var count = Math.Min(segmentsA.Length, segmentsB.Length); + var matched = 0; + var nextSegment = string.Empty; + + for (var i = 0; i < count; i++) + { + if (segmentsA[i] == segmentsB[i]) + { + nextSegment = segmentsA[i]; + matched++; + continue; + } + + break; + } + + if (matched < segmentsA.Length) + { + // Is this the easiest way to tell it's a file? + // Essentially once we know how far we matched we want the next segment along if it exists + // Also add in the trailing separator if it's a directory. + nextSegment = segmentsA[matched] + (matched != segmentsA.Length - 1 || path.Extension.Length == 0 ? ResPath.SeparatorStr : string.Empty); + } + + return new ResPath(nextSegment); + } + /// /// Enumerates segments skipping over first element in ///