80 lines
2.5 KiB
C#
80 lines
2.5 KiB
C#
using System.Security.Cryptography;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Options;
|
|
using QMax.Api.Configuration;
|
|
using QMax.Api.Contracts;
|
|
|
|
namespace QMax.Api.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/app-updates")]
|
|
public sealed class AppUpdatesController(IOptions<QMaxOptions> options) : ControllerBase
|
|
{
|
|
private readonly QMaxOptions _options = options.Value;
|
|
|
|
[AllowAnonymous]
|
|
[HttpGet("android/latest")]
|
|
public async Task<ActionResult<AppUpdateManifestDto>> Latest(CancellationToken cancellationToken)
|
|
{
|
|
var manifestPath = Path.Combine(_options.ReleasesPath, "android", "latest.json");
|
|
if (System.IO.File.Exists(manifestPath))
|
|
{
|
|
return PhysicalFile(manifestPath, "application/json");
|
|
}
|
|
|
|
var androidReleases = new DirectoryInfo(Path.Combine(_options.ReleasesPath, "android"));
|
|
if (!androidReleases.Exists)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var apk = androidReleases
|
|
.GetFiles("*.apk")
|
|
.OrderByDescending(x => x.LastWriteTimeUtc)
|
|
.FirstOrDefault();
|
|
|
|
if (apk is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var manifest = new AppUpdateManifestDto(
|
|
"qmax",
|
|
"QMAX",
|
|
"0.1.0",
|
|
1,
|
|
"stable",
|
|
"android",
|
|
"apk",
|
|
$"/api/app-updates/android/download/{Uri.EscapeDataString(apk.Name)}",
|
|
apk.Length,
|
|
await Sha256Async(apk.FullName, cancellationToken),
|
|
"QMAX local release",
|
|
apk.LastWriteTimeUtc);
|
|
|
|
return manifest;
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[HttpGet("android/download/{fileName}")]
|
|
public IActionResult Download(string fileName)
|
|
{
|
|
var safeName = Path.GetFileName(fileName);
|
|
var path = Path.Combine(_options.ReleasesPath, "android", safeName);
|
|
if (!System.IO.File.Exists(path) || !safeName.EndsWith(".apk", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
return PhysicalFile(path, "application/vnd.android.package-archive", safeName, enableRangeProcessing: true);
|
|
}
|
|
|
|
private static async Task<string> Sha256Async(string path, CancellationToken cancellationToken)
|
|
{
|
|
await using var stream = System.IO.File.OpenRead(path);
|
|
var hash = await SHA256.HashDataAsync(stream, cancellationToken);
|
|
return Convert.ToHexString(hash).ToLowerInvariant();
|
|
}
|
|
}
|