summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI.Web/Controllers/ModsController.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/StardewModdingAPI.Web/Controllers/ModsController.cs')
-rw-r--r--src/StardewModdingAPI.Web/Controllers/ModsController.cs132
1 files changed, 132 insertions, 0 deletions
diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs
new file mode 100644
index 00000000..8fc2cb51
--- /dev/null
+++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Options;
+using StardewModdingAPI.Web.Framework.ConfigModels;
+using StardewModdingAPI.Web.Framework.ModRepositories;
+using StardewModdingAPI.Models;
+
+namespace StardewModdingAPI.Web.Controllers
+{
+ /// <summary>Provides an API to perform mod update checks.</summary>
+ [Produces("application/json")]
+ internal class ModsController : Controller
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The mod repositories which provide mod metadata.</summary>
+ private readonly IDictionary<string, IModRepository> Repositories;
+
+ /// <summary>The cache in which to store mod metadata.</summary>
+ private readonly IMemoryCache Cache;
+
+ /// <summary>The number of minutes update checks should be cached before refetching them.</summary>
+ private readonly int CacheMinutes;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="cache">The cache in which to store mod metadata.</param>
+ /// <param name="configProvider">The config settings for mod update checks.</param>
+ public ModsController(IMemoryCache cache, IOptions<ModUpdateCheckConfig> configProvider)
+ {
+ ModUpdateCheckConfig config = configProvider.Value;
+
+ this.Cache = cache;
+ this.CacheMinutes = config.CacheMinutes;
+
+ this.Repositories =
+ new IModRepository[]
+ {
+ new GitHubRepository(
+ vendorKey: config.GitHubKey,
+ baseUrl: config.GitHubBaseUrl,
+ releaseUrlFormat: config.GitHubReleaseUrlFormat,
+ userAgent: config.GitHubUserAgent,
+ acceptHeader: config.GitHubAcceptHeader,
+ username: config.GitHubUsername,
+ password: config.GitHubPassword
+ ),
+ new NexusRepository(
+ vendorKey: config.NexusKey,
+ userAgent: config.NexusUserAgent,
+ baseUrl: config.NexusBaseUrl,
+ modUrlFormat: config.NexusModUrlFormat
+ )
+ }
+ .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase);
+ }
+
+ /// <summary>Fetch version metadata for the given mods.</summary>
+ /// <param name="modKeys">The namespaced mod keys to search as a comma-delimited array.</param>
+ [HttpGet]
+ public async Task<IDictionary<string, ModInfoModel>> GetAsync(string modKeys)
+ {
+ // sort & filter keys
+ string[] modKeysArray = (modKeys?.Split(',').Select(p => p.Trim()).ToArray() ?? new string[0])
+ .Distinct(StringComparer.CurrentCultureIgnoreCase)
+ .OrderBy(p => p, StringComparer.CurrentCultureIgnoreCase)
+ .ToArray();
+
+ // fetch mod info
+ IDictionary<string, ModInfoModel> result = new Dictionary<string, ModInfoModel>(StringComparer.CurrentCultureIgnoreCase);
+ foreach (string modKey in modKeysArray)
+ {
+ // parse mod key
+ if (!this.TryParseModKey(modKey, out string vendorKey, out string modID))
+ {
+ result[modKey] = new ModInfoModel("The mod key isn't in a valid format. It should contain the mod repository key and mod ID like 'Nexus:541'.");
+ continue;
+ }
+
+ // get matching repository
+ if (!this.Repositories.TryGetValue(vendorKey, out IModRepository repository))
+ {
+ result[modKey] = new ModInfoModel("There's no mod repository matching this namespaced mod ID.");
+ continue;
+ }
+
+ // fetch mod info
+ result[modKey] = await this.Cache.GetOrCreateAsync($"{repository.VendorKey}:{modID}".ToLower(), async entry =>
+ {
+ entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.CacheMinutes);
+ return await repository.GetModInfoAsync(modID);
+ });
+ }
+
+ return result;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Parse a namespaced mod ID.</summary>
+ /// <param name="raw">The raw mod ID to parse.</param>
+ /// <param name="vendorKey">The parsed vendor key.</param>
+ /// <param name="modID">The parsed mod ID.</param>
+ /// <returns>Returns whether the value could be parsed.</returns>
+ private bool TryParseModKey(string raw, out string vendorKey, out string modID)
+ {
+ // split parts
+ string[] parts = raw?.Split(':');
+ if (parts == null || parts.Length != 2)
+ {
+ vendorKey = null;
+ modID = null;
+ return false;
+ }
+
+ // parse
+ vendorKey = parts[0];
+ modID = parts[1];
+ return true;
+ }
+ }
+}