diff --git a/SabreTools.RedumpLib.Test/Data/ExtensionsTests.cs b/SabreTools.RedumpLib.Test/Data/ExtensionsTests.cs index d334699..898ff7e 100644 --- a/SabreTools.RedumpLib.Test/Data/ExtensionsTests.cs +++ b/SabreTools.RedumpLib.Test/Data/ExtensionsTests.cs @@ -3622,6 +3622,113 @@ namespace SabreTools.RedumpLib.Test.Data #endregion + #region Submission Type + + /// + /// Check that every SubmissionType has a long name provided + /// + /// SubmissionType value to check + /// True to expect a null value, false otherwise + [Theory] + [MemberData(nameof(GenerateSubmissionTypeTestData))] + public void SubmissionType_LongName(SubmissionType? sortDirection, bool expectNull) + { + var actual = sortDirection.LongName(); + + if (expectNull) + Assert.Null(actual); + else + Assert.NotNull(actual); + } + + /// + /// Check that every SubmissionType has a short name provided + /// + /// SubmissionType value to check + /// True to expect a null value, false otherwise + [Theory] + [MemberData(nameof(GenerateSubmissionTypeTestData))] + public void SubmissionType_ShortName(SubmissionType? sortDirection, bool expectNull) + { + var actual = sortDirection.ShortName(); + + if (expectNull) + Assert.Null(actual); + else + Assert.NotNull(actual); + } + + /// + /// Ensure that every SubmissionType that has a short name that is unique + /// + [Fact] + public void SubmissionType_ShortName_NoDuplicates() + { + var fullSubmissionTypes = Enum.GetValues().Cast().ToList(); + var filteredSubmissionTypes = new Dictionary(); + + int totalCount = 0; + foreach (SubmissionType? sortDirection in fullSubmissionTypes) + { + var code = sortDirection.ShortName(); + if (string.IsNullOrEmpty(code)) + continue; + + // Throw if the code already exists + if (filteredSubmissionTypes.ContainsKey(code)) + throw new DuplicateNameException($"Code {code} already in dictionary"); + + filteredSubmissionTypes[code] = sortDirection; + totalCount++; + } + + Assert.Equal(totalCount, filteredSubmissionTypes.Count); + } + + /// + /// Check that every SubmissionType can be mapped from a string + /// + /// SubmissionType value to check + /// True to expect a null value, false otherwise + [Theory] + [MemberData(nameof(GenerateSubmissionTypeTestData))] + public void SubmissionType_ToSubmissionType(SubmissionType? sortDirection, bool expectNull) + { + string? longName = sortDirection.LongName(); + string? longNameSpaceless = longName?.Replace(" ", string.Empty); + + var actualNormal = longName.ToSubmissionType(); + var actualSpaceless = longNameSpaceless.ToSubmissionType(); + + if (expectNull) + { + Assert.Null(actualNormal); + Assert.Null(actualSpaceless); + } + else + { + Assert.Equal(sortDirection, actualNormal); + Assert.Equal(sortDirection, actualSpaceless); + } + } + + /// + /// Generate a test set of SubmissionType values + /// + /// MemberData-compatible list of SubmissionType values + public static TheoryData GenerateSubmissionTypeTestData() + { + var testData = new TheoryData() { { null, true } }; + foreach (SubmissionType? sortDirection in Enum.GetValues().Cast()) + { + testData.Add(sortDirection, false); + } + + return testData; + } + + #endregion + #region System Category /// diff --git a/SabreTools.RedumpLib/Data/Constants.cs b/SabreTools.RedumpLib/Data/Constants.cs index 5c33109..0b6e105 100644 --- a/SabreTools.RedumpLib/Data/Constants.cs +++ b/SabreTools.RedumpLib/Data/Constants.cs @@ -142,6 +142,7 @@ namespace SabreTools.RedumpLib.Data DiscSubpath.Cuesheet, DiscSubpath.Edit, DiscSubpath.History, + DiscSubpath.SBI, ]; #endregion diff --git a/SabreTools.RedumpLib/Data/Enumerations.cs b/SabreTools.RedumpLib/Data/Enumerations.cs index d4c0d45..b56ea96 100644 --- a/SabreTools.RedumpLib/Data/Enumerations.cs +++ b/SabreTools.RedumpLib/Data/Enumerations.cs @@ -77,7 +77,6 @@ namespace SabreTools.RedumpLib.Data [HumanReadable(LongName = "MD5", ShortName = "md5")] MD5, - /// Only in redump.org [HumanReadable(LongName = "SBI", ShortName = "sbi")] SBI, @@ -3996,6 +3995,21 @@ namespace SabreTools.RedumpLib.Data Descending, } + /// + /// List of all recognized submission display kinds + /// + public enum SubmissionType + { + [HumanReadable(LongName = "Edit", ShortName = "Edit")] + Edit, + + [HumanReadable(LongName = "New Disc", ShortName = "New+Disc")] + NewDisc, + + [HumanReadable(LongName = "Verification", ShortName = "Verification")] + Verification + } + /// /// List of system categories /// diff --git a/SabreTools.RedumpLib/Data/Extensions.cs b/SabreTools.RedumpLib/Data/Extensions.cs index 90368b5..5af028d 100644 --- a/SabreTools.RedumpLib/Data/Extensions.cs +++ b/SabreTools.RedumpLib/Data/Extensions.cs @@ -2513,6 +2513,70 @@ namespace SabreTools.RedumpLib.Data #endregion + #region Submission Type + + /// + /// Get the human readable name for a SubmissionType + /// + /// + /// + public static string? LongName(this SubmissionType sortDirection) + => AttributeHelper.GetHumanReadableAttribute(sortDirection)?.LongName; + + /// + /// Get the human readable name for a SubmissionType + /// + /// + /// + public static string? LongName(this SubmissionType? sortDirection) + => AttributeHelper.GetHumanReadableAttribute(sortDirection)?.LongName; + + /// + /// Get the URL path part for a SubmissionType + /// + /// + /// + public static string? ShortName(this SubmissionType sortDirection) + => AttributeHelper.GetHumanReadableAttribute(sortDirection)?.ShortName; + + /// + /// Get the URL path part for a SubmissionType + /// + /// + /// + public static string? ShortName(this SubmissionType? sortDirection) + => AttributeHelper.GetHumanReadableAttribute(sortDirection)?.ShortName; + + /// + /// Get the Region enum value for a given string + /// + /// String value to convert + /// Region represented by the string, if possible + public static SubmissionType? ToSubmissionType(this string? sortDirection) + { + // No value means no match + if (sortDirection is null || sortDirection.Length == 0) + return null; + + sortDirection = sortDirection.ToLowerInvariant(); + var sortCategories = (SubmissionType[])Enum.GetValues(typeof(SubmissionType)); + + // Check short names + int index = Array.FindIndex(sortCategories, s => sortDirection == s.ShortName()?.ToLowerInvariant()); + if (index > -1) + return sortCategories[index]; + + // Check long names + index = Array.FindIndex(sortCategories, s => sortDirection == s.LongName()?.ToLowerInvariant() + || sortDirection == s.LongName()?.Replace(" ", string.Empty)?.ToLowerInvariant()); + if (index > -1) + return sortCategories[index]; + + return null; + } + + #endregion + #region System Category /// diff --git a/SabreTools.RedumpLib/Web/UrlBuilder.cs b/SabreTools.RedumpLib/Web/UrlBuilder.cs index 34e8f34..ff55c28 100644 --- a/SabreTools.RedumpLib/Web/UrlBuilder.cs +++ b/SabreTools.RedumpLib/Web/UrlBuilder.cs @@ -18,6 +18,11 @@ namespace SabreTools.RedumpLib.Web #region Top-Level Paths + /// + /// Path for about page + /// + private const string AboutPath = "about/"; + /// /// Path for BIOS datfile downloads /// @@ -26,12 +31,12 @@ namespace SabreTools.RedumpLib.Web /// /// Path for cuesheet pack downloads /// - private const string CuesPath = @"cues/{0}/"; + private const string CuesPath = @"cues/{0}"; /// /// Path for datfile downloads /// - private const string DatfilePath = @"datfile/{0}/"; + private const string DatfilePath = @"datfile/{0}"; /// /// Path for individual disc pages @@ -51,7 +56,7 @@ namespace SabreTools.RedumpLib.Web /// /// Path for key pack downloads /// - private const string KeysPath = @"keys/{0}/"; + private const string KeysPath = @"keys/{0}"; /// /// Path for discs queue @@ -66,7 +71,7 @@ namespace SabreTools.RedumpLib.Web /// /// Path for SBI pack downloads /// - private const string SbiPath = @"sbi/{0}/"; + private const string SbiPath = @"sbi/{0}"; #endregion @@ -74,6 +79,19 @@ namespace SabreTools.RedumpLib.Web #endregion + /// + /// Build a /about/ path URL + /// + public static string BuildAboutUrl() + { + var sb = new StringBuilder(); + + sb.Append(SiteBaseUrl); + sb.Append(AboutPath); + + return sb.ToString(); + } + /// /// Build a /static/bios/ path URL /// @@ -365,13 +383,17 @@ namespace SabreTools.RedumpLib.Web /// /// Build a /downloads/ path URL /// - public static string BuildDownloadsUrl() + /// Target database download + public static string BuildDownloadsUrl(bool? database = null) { var sb = new StringBuilder(); sb.Append(SiteBaseUrl); sb.Append(DownloadsPath); + if (database == true) + sb.Append("database"); + return sb.ToString(); } @@ -381,6 +403,7 @@ namespace SabreTools.RedumpLib.Web /// Pack type /// System for download /// Does not check for invalid systems + /// TODO: Handle download_dat_variant? public static string BuildPackUrl(PackType packType, PhysicalSystem system) { var sb = new StringBuilder(); @@ -408,12 +431,94 @@ namespace SabreTools.RedumpLib.Web /// /// Build a /queue/ path URL /// - public static string BuildQueueUrl() + /// Add disc ID to filter, null to omit + /// Set disc history status, null to omit + /// Add sorting direction, null to omit + /// Page number, null to omit + /// Add sorting type, null to omit + /// Add status to filter, null to omit + /// Add submitter name to filter, null to omit + /// Add submission type to filter, null to omit + /// Add system to filter, null to omit + /// Ordered according to site source code + public static string BuildQueueUrl( + long? discId = null, + bool? isDiscHistory = null, + SortDirection? order = null, + long? page = null, + SortCategory? sort = null, + DumpStatus? status = null, + string? submitter = null, + SubmissionType? subType = null, + PhysicalSystem? system = null) { var sb = new StringBuilder(); sb.Append(SiteBaseUrl); sb.Append(QueuePath); + sb.Append('?'); + + // Status + string? statusName = status.LongName(); + if (statusName is not null) + sb.Append($"status={statusName}&"); + + // Submission Type + string? subTypeName = subType.ShortName(); + if (subTypeName is not null) + sb.Append($"sub_type={subTypeName}&"); + + // System + string? systemName = system.ShortName(); + if (systemName is not null) + sb.Append($"system={systemName}&"); + + // Submitter + if (submitter is not null) + sb.Append($"submitter={submitter}&"); + + // Disc ID + if (discId is not null) + sb.Append($"disc_id={discId}&"); + + // Sorting + switch (sort) + { + case SortCategory.Title: + case SortCategory.Added: + case SortCategory.Region: + case SortCategory.System: + case SortCategory.Version: + case SortCategory.Edition: + case SortCategory.Language: + case SortCategory.Languages: + case SortCategory.Serial: + case SortCategory.Status: + case SortCategory.Modified: + sb.Append($"sort={sort.ShortName()}&"); + break; + + default: break; + } + + // Sort Direction + switch (order) + { + case SortDirection.Ascending: + case SortDirection.Descending: + sb.Append($"order={order.ShortName()}&"); + break; + + default: break; + } + + // Page Number + if (page is not null) + sb.Append($"page={page}"); + + // Is Disc History + if (isDiscHistory is not null) + sb.Append($"is_disc_history={isDiscHistory.ToYesNo().LongName()}&"); return sb.ToString(); }