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();
}