-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
Merge pull request #2479 from Nexus-Mods/update-individual-files
Added: Fuzzy Search/File Map for Detecting File Updates
Showing
12 changed files
with
745 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
src/Networking/NexusMods.Networking.ModUpdates/FuzzySearch.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
using System.Text.RegularExpressions; | ||
namespace NexusMods.Networking.ModUpdates; | ||
|
||
/// <summary> | ||
/// This class has all the code around the 'fuzzy search' feature documented in | ||
/// `0019-updating-mods.md` page of the wiki. | ||
/// </summary> | ||
public static class FuzzySearch | ||
{ | ||
private static readonly string[] KnownFileExtensions = | ||
[ | ||
".rar", ".zip", ".7z", ".exe", ".omod", ".nx", | ||
]; | ||
|
||
/// <summary> | ||
/// Normalizes a filename by: | ||
/// 1. Stripping all possible version number variants | ||
/// 2. Replacing underscores with spaces | ||
/// 3. Stripping file extensions | ||
/// 4. Converting to lowercase for case-insensitive comparison | ||
/// </summary> | ||
/// <param name="fileName">The filename to normalize</param> | ||
/// <param name="version">The version to strip from the filename</param> | ||
/// <returns>The normalized filename</returns> | ||
public static string NormalizeFileName(string? fileName, string? version) | ||
{ | ||
if (string.IsNullOrWhiteSpace(fileName)) | ||
return string.Empty; | ||
|
||
var result = fileName; | ||
|
||
// Strip all possible version number variants | ||
if (!string.IsNullOrWhiteSpace(version)) | ||
{ | ||
// Sort by length, to ensure we don't match a substring containing another string | ||
var versionVariants = GetVersionPermutations(version).OrderByDescending(x => x.Length); | ||
foreach (var variant in versionVariants) | ||
result = result.Replace(variant, string.Empty, StringComparison.OrdinalIgnoreCase); | ||
} | ||
|
||
// Replace underscores with spaces | ||
result = result.Replace('_', ' '); | ||
|
||
// Strip extensions | ||
result = StripFileExtension(result); | ||
|
||
// Normalize whitespace (trim and reduce multiple spaces to single) | ||
result = string.Join(" ", result.Split([' '], | ||
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); | ||
|
||
// Convert to lowercase for case-insensitive comparison | ||
return result.ToLowerInvariant(); | ||
} | ||
|
||
/// <summary> | ||
/// Given an existing version string, return all possible variations that | ||
/// a user may place inside the file name on a mod page. | ||
/// | ||
/// Refer to tests of this for examples. | ||
/// </summary> | ||
/// <param name="version">The original version string attached</param> | ||
public static string[] GetVersionPermutations(string version) | ||
{ | ||
if (string.IsNullOrWhiteSpace(version)) | ||
return []; | ||
|
||
var results = new HashSet<string>(8); | ||
void AddIfValid(string value) | ||
{ | ||
if (!string.IsNullOrWhiteSpace(value)) | ||
results.Add(value); | ||
} | ||
|
||
void AddVariants(string input) | ||
{ | ||
AddIfValid(input); | ||
AddIfValid(input.Replace(".", "_")); | ||
} | ||
|
||
// Add original version and its variants | ||
AddVariants(version); | ||
|
||
// Remove version suffix. | ||
var withoutTextSuffix = version; | ||
for (var x = version.Length - 1; x >= 0; x--) | ||
{ | ||
if (!char.IsNumber(version[x])) continue; | ||
withoutTextSuffix = version.Substring(0, x + 1); | ||
break; | ||
} | ||
|
||
if (withoutTextSuffix != version) | ||
AddVariants(withoutTextSuffix); | ||
|
||
// Handle Single letter version prefixes variants ('v' for version, 'a' for 'alpha', 'b' for 'beta', etc.) | ||
if (version.Length > 0 && char.IsLetter(version[0])) | ||
{ | ||
var withoutPrefix = version.Substring(1); | ||
AddVariants(withoutPrefix); | ||
|
||
// Also add variants without both prefix and suffix | ||
if (withoutTextSuffix != version) | ||
AddVariants(withoutPrefix.Substring(0, withoutTextSuffix.Length - 1)); | ||
} | ||
|
||
return results.ToArray(); | ||
} | ||
|
||
/// <summary> | ||
/// Strips known file extensions from the end of a filename | ||
/// </summary> | ||
/// <param name="fileName">The filename to process</param> | ||
/// <returns>Filename with any known extension removed</returns> | ||
public static string StripFileExtension(string fileName) | ||
{ | ||
if (string.IsNullOrWhiteSpace(fileName)) | ||
return fileName; | ||
|
||
foreach (var ext in KnownFileExtensions) | ||
{ | ||
if (fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) | ||
return fileName[..^ext.Length]; | ||
} | ||
|
||
return fileName; | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
src/Networking/NexusMods.Networking.ModUpdates/Mixins/ModFileMetadataMixin.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using NexusMods.Abstractions.NexusModsLibrary; | ||
using NexusMods.Networking.ModUpdates.Traits; | ||
namespace NexusMods.Networking.ModUpdates.Mixins; | ||
|
||
/// <summary> | ||
/// A mixin for mod file metadata that allows you to integrate mod file results | ||
/// from the Nexus API. | ||
/// </summary> | ||
public class ModFileMetadataMixin : IAmAModFile | ||
{ | ||
/// <inheritdoc /> | ||
public string Name => Metadata.Name; | ||
|
||
/// <inheritdoc /> | ||
public string Version => Metadata.Version; | ||
|
||
/// <inheritdoc /> | ||
public DateTimeOffset UploadedAt => Metadata.UploadedAt; | ||
|
||
/// <inheritdoc /> | ||
public IEnumerable<IAmAModFile> OtherFilesInSameModPage => | ||
Metadata.ModPage.Files.Select(f => new ModFileMetadataMixin(f)); | ||
|
||
/// <summary/> | ||
public NexusModsFileMetadata.ReadOnly Metadata { get; } | ||
|
||
/// <summary/> | ||
public ModFileMetadataMixin(NexusModsFileMetadata.ReadOnly metadata) => Metadata = metadata; | ||
|
||
/// <summary> | ||
/// Returns a normalized name and version. | ||
/// </summary> | ||
public string GetNormalizedFileName() => FuzzySearch.NormalizeFileName(Name, Version); | ||
} |
Oops, something went wrong.