using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using StardewModdingAPI.Toolkit.Utilities;
using StardewModdingAPI.Web.Framework;
using StardewModdingAPI.Web.Framework.Clients.Pastebin;
using StardewModdingAPI.Web.Framework.Compression;
using StardewModdingAPI.Web.Framework.ConfigModels;
using StardewModdingAPI.Web.Framework.LogParsing;
using StardewModdingAPI.Web.Framework.LogParsing.Models;
using StardewModdingAPI.Web.ViewModels;
namespace StardewModdingAPI.Web.Controllers
{
/// Provides a web UI and API for parsing SMAPI log files.
internal class LogParserController : Controller
{
/*********
** Fields
*********/
/// The site config settings.
private readonly SiteConfig Config;
/// The underlying Pastebin client.
private readonly IPastebinClient Pastebin;
/// The underlying text compression helper.
private readonly IGzipHelper GzipHelper;
/*********
** Public methods
*********/
/***
** Constructor
***/
/// Construct an instance.
/// The context config settings.
/// The Pastebin API client.
/// The underlying text compression helper.
public LogParserController(IOptions siteConfig, IPastebinClient pastebin, IGzipHelper gzipHelper)
{
this.Config = siteConfig.Value;
this.Pastebin = pastebin;
this.GzipHelper = gzipHelper;
}
/***
** Web UI
***/
/// Render the log parser UI.
/// The paste ID.
/// Whether to display the raw unparsed log.
[HttpGet]
[Route("log")]
[Route("log/{id}")]
public async Task Index(string id = null, bool raw = false)
{
// fresh page
if (string.IsNullOrWhiteSpace(id))
return this.View("Index", this.GetModel(id));
// log page
PasteInfo paste = await this.GetAsync(id);
ParsedLog log = paste.Success
? new LogParser().Parse(paste.Content)
: new ParsedLog { IsValid = false, Error = "Pastebin error: " + paste.Error };
return this.View("Index", this.GetModel(id).SetResult(log, raw));
}
/***
** JSON
***/
/// Save raw log data.
[HttpPost, AllowLargePosts]
[Route("log")]
public async Task PostAsync()
{
// get raw log text
string input = this.Request.Form["input"].FirstOrDefault();
if (string.IsNullOrWhiteSpace(input))
return this.View("Index", this.GetModel(null, uploadError: "The log file seems to be empty."));
// upload log
input = this.GzipHelper.CompressString(input);
SavePasteResult result = await this.Pastebin.PostAsync($"SMAPI log {DateTime.UtcNow:s}", input);
// handle errors
if (!result.Success)
return this.View("Index", this.GetModel(result.ID, uploadError: $"Pastebin error: {result.Error ?? "unknown error"}"));
// redirect to view
UriBuilder uri = new UriBuilder(new Uri(this.Config.LogParserUrl));
uri.Path = uri.Path.TrimEnd('/') + '/' + result.ID;
return this.Redirect(uri.Uri.ToString());
}
/*********
** Private methods
*********/
/// Fetch raw text from Pastebin.
/// The Pastebin paste ID.
private async Task GetAsync(string id)
{
PasteInfo response = await this.Pastebin.GetAsync(id);
response.Content = this.GzipHelper.DecompressString(response.Content);
return response;
}
/// Build a log parser model.
/// The paste ID.
/// An error which occurred while uploading the log to Pastebin.
private LogParserModel GetModel(string pasteID, string uploadError = null)
{
string sectionUrl = this.Config.LogParserUrl;
Platform? platform = this.DetectClientPlatform();
return new LogParserModel(sectionUrl, pasteID, platform) { UploadError = uploadError };
}
/// Detect the viewer's OS.
/// Returns the viewer OS if known, else null.
private Platform? DetectClientPlatform()
{
string userAgent = this.Request.Headers["User-Agent"];
switch (userAgent)
{
case string ua when ua.Contains("Windows"):
return Platform.Windows;
case string ua when ua.Contains("Android"): // check for Android before Linux because Android user agents also contain Linux
return Platform.Android;
case string ua when ua.Contains("Linux"):
return Platform.Linux;
case string ua when ua.Contains("Mac"):
return Platform.Mac;
default:
return null;
}
}
}
}