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
130
131
132
|
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using StardewModdingAPI.Internal;
using StardewModdingAPI.Web.Framework.Clients.GitHub;
using StardewModdingAPI.Web.ViewModels;
namespace StardewModdingAPI.Web.Controllers
{
/// <summary>Provides an info/download page about SMAPI.</summary>
[Route("")]
[Route("install")]
internal class IndexController : Controller
{
/*********
** Properties
*********/
/// <summary>The cache in which to store release data.</summary>
private readonly IMemoryCache Cache;
/// <summary>The GitHub API client.</summary>
private readonly IGitHubClient GitHub;
/// <summary>The cache time for release info.</summary>
private readonly TimeSpan CacheTime = TimeSpan.FromSeconds(1);
/// <summary>The GitHub repository name to check for update.</summary>
private readonly string RepositoryName = "Pathoschild/SMAPI";
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="cache">The cache in which to store release data.</param>
/// <param name="github">The GitHub API client.</param>
public IndexController(IMemoryCache cache, IGitHubClient github)
{
this.Cache = cache;
this.GitHub = github;
}
/// <summary>Display the index page.</summary>
[HttpGet]
public async Task<ViewResult> Index()
{
// fetch SMAPI releases
IndexVersionModel stableVersion = await this.Cache.GetOrCreateAsync("stable-version", async entry =>
{
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.Add(this.CacheTime);
GitRelease release = await this.GitHub.GetLatestReleaseAsync(this.RepositoryName, includePrerelease: false);
return new IndexVersionModel(release.Name, release.Body, this.GetMainDownloadUrl(release), this.GetDevDownloadUrl(release));
});
IndexVersionModel betaVersion = await this.Cache.GetOrCreateAsync("beta-version", async entry =>
{
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.Add(this.CacheTime);
GitRelease release = await this.GitHub.GetLatestReleaseAsync(this.RepositoryName, includePrerelease: true);
return release.IsPrerelease
? this.GetBetaDownload(release)
: null;
});
// render view
var model = new IndexModel(stableVersion, betaVersion);
return this.View(model);
}
/*********
** Private methods
*********/
/// <summary>Get the main download URL for a SMAPI release.</summary>
/// <param name="release">The SMAPI release.</param>
private string GetMainDownloadUrl(GitRelease release)
{
// get main download URL
foreach (GitAsset asset in release.Assets ?? new GitAsset[0])
{
if (Regex.IsMatch(asset.FileName, @"SMAPI-[\d\.]+-installer.zip"))
return asset.DownloadUrl;
}
// fallback just in case
return "https://github.com/pathoschild/SMAPI/releases";
}
/// <summary>Get the for-developers download URL for a SMAPI release.</summary>
/// <param name="release">The SMAPI release.</param>
private string GetDevDownloadUrl(GitRelease release)
{
// get dev download URL
foreach (GitAsset asset in release.Assets ?? new GitAsset[0])
{
if (Regex.IsMatch(asset.FileName, @"SMAPI-[\d\.]+-installer-for-developers.zip"))
return asset.DownloadUrl;
}
// fallback just in case
return "https://github.com/pathoschild/SMAPI/releases";
}
/// <summary>Get the latest beta download for a SMAPI release.</summary>
/// <param name="release">The SMAPI release.</param>
private IndexVersionModel GetBetaDownload(GitRelease release)
{
// get download with the latest version
SemanticVersionImpl latestVersion = null;
string latestUrl = null;
foreach (GitAsset asset in release.Assets ?? new GitAsset[0])
{
// parse version
Match versionMatch = Regex.Match(asset.FileName, @"SMAPI-([\d\.]+(?:-.+)?)-installer.zip");
if (!versionMatch.Success || !SemanticVersionImpl.TryParse(versionMatch.Groups[1].Value, out SemanticVersionImpl version))
continue;
// save latest version
if (latestVersion == null || latestVersion.CompareTo(version) < 0)
{
latestVersion = version;
latestUrl = asset.DownloadUrl;
}
}
// return if prerelease
return latestVersion?.Tag != null
? new IndexVersionModel(latestVersion.ToString(), release.Body, latestUrl, null)
: null;
}
}
}
|