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 { /// Provides an info/download page about SMAPI. [Route("")] [Route("install")] internal class IndexController : Controller { /********* ** Properties *********/ /// The cache in which to store release data. private readonly IMemoryCache Cache; /// The GitHub API client. private readonly IGitHubClient GitHub; /// The cache time for release info. private readonly TimeSpan CacheTime = TimeSpan.FromSeconds(1); /// The GitHub repository name to check for update. private readonly string RepositoryName = "Pathoschild/SMAPI"; /********* ** Public methods *********/ /// Construct an instance. /// The cache in which to store release data. /// The GitHub API client. public IndexController(IMemoryCache cache, IGitHubClient github) { this.Cache = cache; this.GitHub = github; } /// Display the index page. [HttpGet] public async Task 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 *********/ /// Get the main download URL for a SMAPI release. /// The SMAPI release. 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"; } /// Get the for-developers download URL for a SMAPI release. /// The SMAPI release. 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"; } /// Get the latest beta download for a SMAPI release. /// The SMAPI release. 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; } } }