1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
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.Web.Models;
namespace StardewModdingAPI.Web.Controllers
{
/// <summary>Provides an API to perform mod update checks.</summary>
[Produces("application/json")]
public 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 NexusRepository(config.NexusKey, config.NexusUserAgent, config.NexusBaseUrl, 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)
{
return await this.PostAsync(new ModSearchModel
{
ModKeys = modKeys?.Split(',').Select(p => p.Trim()).ToArray() ?? new string[0]
});
}
/// <summary>Fetch version metadata for the given mods.</summary>
/// <param name="search">The search options.</param>
[HttpPost]
public async Task<IDictionary<string, ModInfoModel>> PostAsync([FromBody] ModSearchModel search)
{
// sort & filter keys
string[] modKeys = (search.ModKeys ?? 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 modKeys)
{
// 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;
}
}
}
|