// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.IO; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; namespace osu.Game.Extensions { public static class ModelExtensions { /// /// Get the relative path in osu! storage for this file. /// /// The file info. /// A relative file path. public static string GetStoragePath(this IFileInfo fileInfo) => Path.Combine(fileInfo.Hash.Remove(1), fileInfo.Hash.Remove(2), fileInfo.Hash); /// /// Returns a user-facing string representing the . /// /// /// /// Non-interface types without special handling will fall back to . /// /// /// Warning: This method is _purposefully_ not called GetDisplayTitle() like the others, because otherwise /// extension method type inference rules cause this method to call itself and cause a stack overflow. /// /// public static string GetDisplayString(this object? model) { string? result = null; switch (model) { case IBeatmapSetInfo beatmapSetInfo: result = beatmapSetInfo.Metadata.GetDisplayTitle(); break; case IBeatmapInfo beatmapInfo: result = beatmapInfo.GetDisplayTitle(); break; case IBeatmapMetadataInfo metadataInfo: result = metadataInfo.GetDisplayTitle(); break; case IScoreInfo scoreInfo: result = scoreInfo.GetDisplayTitle(); break; case IRulesetInfo rulesetInfo: result = rulesetInfo.Name; break; case IUser user: result = user.Username; break; } // fallback in case none of the above happens to match. result ??= model?.ToString() ?? @"null"; return result; } /// /// Check whether this 's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). /// public static bool IsLegacyRuleset(this IRulesetInfo ruleset) => ruleset.OnlineID >= 0 && ruleset.OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; /// /// Check whether the online ID of two s match. /// /// The instance to compare. /// The other instance to compare against. /// Whether online IDs match. If either instance is missing an online ID, this will return false. public static bool MatchesOnlineID(this IBeatmapSetInfo? instance, IBeatmapSetInfo? other) => matchesOnlineID(instance, other); /// /// Check whether the online ID of two s match. /// /// The instance to compare. /// The other instance to compare against. /// Whether online IDs match. If either instance is missing an online ID, this will return false. public static bool MatchesOnlineID(this IBeatmapInfo? instance, IBeatmapInfo? other) => matchesOnlineID(instance, other); /// /// Check whether the online ID of two s match. /// /// The instance to compare. /// The other instance to compare against. /// Whether online IDs match. If either instance is missing an online ID, this will return false. public static bool MatchesOnlineID(this IRulesetInfo? instance, IRulesetInfo? other) => matchesOnlineID(instance, other); /// /// Check whether the online ID of two s match. /// /// The instance to compare. /// The other instance to compare against. /// Whether online IDs match. If either instance is missing an online ID, this will return false. public static bool MatchesOnlineID(this APIUser? instance, APIUser? other) => matchesOnlineID(instance, other); /// /// Check whether the online ID of two s match. /// /// The instance to compare. /// The other instance to compare against. /// /// Whether online IDs match. /// Both and are checked, in that order. /// If either instance is missing an online ID, this will return false. /// public static bool MatchesOnlineID(this IScoreInfo? instance, IScoreInfo? other) { if (matchesOnlineID(instance, other)) return true; if (instance == null || other == null) return false; if (instance.LegacyOnlineID < 0 || other.LegacyOnlineID < 0) return false; return instance.LegacyOnlineID.Equals(other.LegacyOnlineID); } private static bool matchesOnlineID(this IHasOnlineID? instance, IHasOnlineID? other) { if (instance == null || other == null) return false; if (instance.OnlineID < 0 || other.OnlineID < 0) return false; return instance.OnlineID.Equals(other.OnlineID); } private static bool matchesOnlineID(this IHasOnlineID? instance, IHasOnlineID? other) { if (instance == null || other == null) return false; if (instance.OnlineID < 0 || other.OnlineID < 0) return false; return instance.OnlineID.Equals(other.OnlineID); } // intentionally chosen to match stable. // see https://referencesource.microsoft.com/#mscorlib/system/io/path.cs,88 private static readonly char[] invalid_filename_chars = { '\"', '<', '>', '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31, ':', '*', '?', '\\', '/' }; /// /// Create a valid filename which should work across all platforms. /// /// /// /// We are using this in place of /// as that function works per-platform, and therefore returns a different set of characters on different OSes. /// /// /// Note that the behaviour of this method is LOAD-BEARING for things such as interoperability of beatmap exports with stable, /// especially with respect to beatmap submission. /// DO NOT CHANGE THE SEMANTICS OF THIS METHOD unless you know well what you are doing. /// /// /// public static string GetValidFilename(this string filename) { foreach (char c in invalid_filename_chars) filename = filename.Replace(c.ToString(), string.Empty); return filename; } public static bool RequiresSupporter(this BeatmapLeaderboardScope scope, bool filterMods) { switch (scope) { case BeatmapLeaderboardScope.Local: return false; case BeatmapLeaderboardScope.Country: case BeatmapLeaderboardScope.Friend: return true; } return filterMods; } } }