Allow for cancelling downloads from filesystem
Rebuild request orignally will use setting for constructing the url Rebuild request from client via no-cache will use httppcontext to get runtime pathing to generate url Escape the url generated
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -13,6 +13,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConcurrentDictionary_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F8d7a996932951dc929c6c3855f98f0c58cae8ac5b0d0bbf223f97c6388b3b61f_003FConcurrentDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConcurrentDictionary_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F8d7a996932951dc929c6c3855f98f0c58cae8ac5b0d0bbf223f97c6388b3b61f_003FConcurrentDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AControllerBase_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F6fa05d7dbf2e454ce78b261b422234da1f1ccedee678fd997e5ef4afe6df6e_003FControllerBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AControllerBase_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F6fa05d7dbf2e454ce78b261b422234da1f1ccedee678fd997e5ef4afe6df6e_003FControllerBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACryptoUtil_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F77078e9a1d254191bb508f54a277fc6e1c2e00_003Fd4_003F032ece9d_003FCryptoUtil_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACryptoUtil_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F77078e9a1d254191bb508f54a277fc6e1c2e00_003Fd4_003F032ece9d_003FCryptoUtil_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADefaultHttpContext_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F6af28599e71724c5fc6a617444e2f60b5d57dfdc5be0df4ba43ccfc36977_003FDefaultHttpContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe5d623ea960f2c3c9fda144954d339f8d4cd3dad6dd8bd4ab96093a010ab_003FDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe5d623ea960f2c3c9fda144954d339f8d4cd3dad6dd8bd4ab96093a010ab_003FDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExecutionContext_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F53b0d531c06faf86bf2ae111a9a2dbce4c52a9153feb9966ade60289c71bf52_003FExecutionContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExecutionContext_002Ecs_002Fl_003AC_0021_003FUsers_003Fecens_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F53b0d531c06faf86bf2ae111a9a2dbce4c52a9153feb9966ade60289c71bf52_003FExecutionContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace TinfoilVibeServer.Controllers;
|
||||||
|
|
||||||
|
public class CancelableFileResult : FileResult
|
||||||
|
{
|
||||||
|
private readonly Stream _fileStream;
|
||||||
|
|
||||||
|
public CancelableFileResult(string contentType, Stream fileStream)
|
||||||
|
: base(contentType)
|
||||||
|
{
|
||||||
|
_fileStream = fileStream ?? throw new ArgumentNullException(nameof(fileStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows you to set a suggested download name.
|
||||||
|
/// It will be sent in a “Content‑Disposition” header.
|
||||||
|
/// </summary>
|
||||||
|
public string? FileDownloadName { get; set; }
|
||||||
|
|
||||||
|
public override async Task ExecuteResultAsync(ActionContext context)
|
||||||
|
{
|
||||||
|
var response = context.HttpContext.Response;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(FileDownloadName))
|
||||||
|
{
|
||||||
|
// Typical “attachment” disposition – most browsers honour it.
|
||||||
|
response.Headers.Append("Content-Disposition",
|
||||||
|
$"attachment; filename=\"{FileDownloadName}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request‑aborted token tells the stream copy to stop ASAP
|
||||||
|
var cancellationToken = context.HttpContext.RequestAborted;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Copy the file to the response body in 8 KiB chunks
|
||||||
|
await _fileStream.CopyToAsync(
|
||||||
|
response.Body,
|
||||||
|
bufferSize: 81920, // 80 KiB – default for Stream.CopyToAsync
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// The client disconnected – nothing to do.
|
||||||
|
// Swallowing keeps the API from returning a 500 error.
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await _fileStream.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ public sealed class IndexController : ControllerBase
|
|||||||
_indexBuilderService.InvalidateIndex(this, EventArgs.Empty);
|
_indexBuilderService.InvalidateIndex(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = _indexBuilderService.Build();
|
var index = _indexBuilderService.Build(HttpContext);
|
||||||
|
|
||||||
return Ok(index);
|
return Ok(index);
|
||||||
}
|
}
|
||||||
@@ -83,13 +83,27 @@ public sealed class IndexController : ControllerBase
|
|||||||
|
|
||||||
// ---- 3️⃣ If the file is a normal NSP → send it ----------------
|
// ---- 3️⃣ If the file is a normal NSP → send it ----------------
|
||||||
|
|
||||||
if (Path.GetExtension(entry.Path).Equals(".nsp", StringComparison.OrdinalIgnoreCase) && !entry.Path.Contains(_snapshotService.GetArchivePathSeparator()))
|
if (Path.GetExtension(entry.Path).Equals(".nsp", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !entry.Path.Contains(_snapshotService.GetArchivePathSeparator()))
|
||||||
{
|
{
|
||||||
if (System.IO.File.Exists(entry.Path))
|
if (System.IO.File.Exists(entry.Path))
|
||||||
{
|
{
|
||||||
// Regular file – just serve it.
|
// 1️⃣ Open the file for async read
|
||||||
return PhysicalFile(entry.Path, "application/octet-stream",
|
var fileStream = new FileStream(
|
||||||
Path.GetFileName(entry.Path));
|
entry.Path,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.Read,
|
||||||
|
bufferSize: 128 * 1024 * 1024, // 81920, // 80 KiB
|
||||||
|
useAsync: true); // <‑‑ VERY important for scalability
|
||||||
|
|
||||||
|
// 2️⃣ Return a cancellation‑aware result
|
||||||
|
return new CancelableFileResult(
|
||||||
|
contentType: "application/octet-stream",
|
||||||
|
fileStream: fileStream)
|
||||||
|
{
|
||||||
|
FileDownloadName = Path.GetFileName(entry.Path) // optional but nice
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +234,12 @@ public sealed class IndexController : ControllerBase
|
|||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count);
|
public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count);
|
||||||
|
|
||||||
|
|
||||||
|
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public override bool CanRead => _innerStream.CanRead;
|
public override bool CanRead => _innerStream.CanRead;
|
||||||
public override bool CanSeek => _innerStream.CanSeek;
|
public override bool CanSeek => _innerStream.CanSeek;
|
||||||
public override bool CanWrite => _innerStream.CanWrite;
|
public override bool CanWrite => _innerStream.CanWrite;
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public sealed class BasicAuthMiddleware
|
|||||||
{
|
{
|
||||||
logger.LogWarning("Auth failed for user {User} from {IP}: {Error}", username, ip, error);
|
logger.LogWarning("Auth failed for user {User} from {IP}: {Error}", username, ip, error);
|
||||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||||
context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"FileSnapshot\"");
|
context.Response.Headers.Append("WWW-Authenticate", "Basic realm=\"FileSnapshot\"");
|
||||||
await context.Response.WriteAsync(error ?? "Unauthorized");
|
await context.Response.WriteAsync(error ?? "Unauthorized");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -105,6 +105,6 @@ public sealed class BasicAuthMiddleware
|
|||||||
private static void Challenge(HttpContext ctx)
|
private static void Challenge(HttpContext ctx)
|
||||||
{
|
{
|
||||||
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||||
ctx.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"FileSnapshot\"");
|
ctx.Response.Headers.Append("WWW-Authenticate", "Basic realm=\"FileSnapshot\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace TinfoilVibeServer.Models;
|
||||||
|
|
||||||
|
public class IndexBuilderSettings
|
||||||
|
{
|
||||||
|
public string ApiBaseUrl { get; set; } = "http://tinfoil.localhost";
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": false,
|
"launchBrowser": false,
|
||||||
"applicationUrl": "http://192.168.1.145:80",
|
"applicationUrl": "http://192.168.1.145:80;http://tinfoil.localhost:8080;http://tinfoil.ecenshu.net",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using TinfoilVibeServer.Models;
|
using TinfoilVibeServer.Models;
|
||||||
|
|
||||||
namespace TinfoilVibeServer.Services;
|
namespace TinfoilVibeServer.Services;
|
||||||
@@ -12,6 +13,7 @@ public sealed class IndexBuilderService: IHostedService
|
|||||||
{
|
{
|
||||||
private const string CacheFileName = "indexcache.json";
|
private const string CacheFileName = "indexcache.json";
|
||||||
|
|
||||||
|
private readonly IOptions<IndexBuilderSettings> _options;
|
||||||
private readonly ISnapshotService _snapshotService;
|
private readonly ISnapshotService _snapshotService;
|
||||||
private readonly TitleDatabaseService _titleDb;
|
private readonly TitleDatabaseService _titleDb;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
@@ -20,11 +22,13 @@ public sealed class IndexBuilderService: IHostedService
|
|||||||
|
|
||||||
private readonly SemaphoreSlim _lock = new(1, 1);
|
private readonly SemaphoreSlim _lock = new(1, 1);
|
||||||
public IndexBuilderService(
|
public IndexBuilderService(
|
||||||
|
IOptions<IndexBuilderSettings> options,
|
||||||
ISnapshotService snapshotService,
|
ISnapshotService snapshotService,
|
||||||
TitleDatabaseService titleDb,
|
TitleDatabaseService titleDb,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
ILogger<IndexBuilderService> logger)
|
ILogger<IndexBuilderService> logger)
|
||||||
{
|
{
|
||||||
|
_options = options;
|
||||||
_snapshotService = snapshotService;
|
_snapshotService = snapshotService;
|
||||||
_titleDb = titleDb;
|
_titleDb = titleDb;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
@@ -32,7 +36,7 @@ public sealed class IndexBuilderService: IHostedService
|
|||||||
_cachePath = Path.Combine(AppContext.BaseDirectory, CacheFileName);
|
_cachePath = Path.Combine(AppContext.BaseDirectory, CacheFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IndexDto Build()
|
public IndexDto Build(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
// 1️⃣ Load cache if it exists
|
// 1️⃣ Load cache if it exists
|
||||||
var cached = LoadCache();
|
var cached = LoadCache();
|
||||||
@@ -54,7 +58,7 @@ public sealed class IndexBuilderService: IHostedService
|
|||||||
|
|
||||||
_logger.LogInformation("Building index (snapshot size={Count})", snapshot.Files.Count);
|
_logger.LogInformation("Building index (snapshot size={Count})", snapshot.Files.Count);
|
||||||
// 3️⃣ Build new index from snapshot entries
|
// 3️⃣ Build new index from snapshot entries
|
||||||
var files = ParseSnapshotFiles(snapshot);
|
var files = ParseSnapshotFiles(snapshot, new Uri(httpContext.Request.Scheme + "://" + httpContext.Request.Host + httpContext.Request.PathBase));
|
||||||
|
|
||||||
var directories = _configuration.GetSection("Directories")
|
var directories = _configuration.GetSection("Directories")
|
||||||
.Get<string[]>() ?? Array.Empty<string>();
|
.Get<string[]>() ?? Array.Empty<string>();
|
||||||
@@ -69,7 +73,7 @@ public sealed class IndexBuilderService: IHostedService
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<List<FileDto>> ParseSnapshotFiles(SnapshotService.ROMSnapshot snapshot)
|
private List<List<FileDto>> ParseSnapshotFiles(SnapshotService.ROMSnapshot snapshot, Uri baseUri)
|
||||||
{
|
{
|
||||||
var files = snapshot.Files
|
var files = snapshot.Files
|
||||||
.Where(e => e.Titles.Count > 0)
|
.Where(e => e.Titles.Count > 0)
|
||||||
@@ -108,9 +112,16 @@ public sealed class IndexBuilderService: IHostedService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = $"http://192.168.1.145/{name}[{titleId}][v{versionNumberParsed}][{patchOrApp}].nsp";
|
var fileName =Uri.EscapeDataString($"{name}[{titleId}][v{versionNumberParsed}][{patchOrApp}].nsp");
|
||||||
|
var url = $"{baseUri.ToString().TrimEnd('/')}/{fileName}";
|
||||||
fileDtos.Add(new FileDto(url, e.Size));
|
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||||
|
{
|
||||||
|
fileDtos.Add(new FileDto(url, e.Size));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Invalid URL for {TitleId}: {Url}", titleId, url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileDtos;
|
return fileDtos;
|
||||||
@@ -150,8 +161,10 @@ public sealed class IndexBuilderService: IHostedService
|
|||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Build();
|
var url = new Uri(_options.Value.ApiBaseUrl);
|
||||||
this._snapshotService.SnapshotRebuilt += InvalidateIndex;
|
var host = url.Host;
|
||||||
|
Build(new DefaultHttpContext {HttpContext = { Request = { Host = new HostString(host), Scheme = url.Scheme, Path = new PathString(url.AbsolutePath)}}});
|
||||||
|
_snapshotService.SnapshotRebuilt += InvalidateIndex;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -649,7 +649,7 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ
|
|||||||
|
|
||||||
public class ROMSnapshot
|
public class ROMSnapshot
|
||||||
{
|
{
|
||||||
public string Hash { get; set; }
|
public string? Hash { get; set; }
|
||||||
public IReadOnlyList<FileEntry> Files { get; set; } = new List<FileEntry>();
|
public IReadOnlyList<FileEntry> Files { get; set; } = new List<FileEntry>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,8 +664,9 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ
|
|||||||
new Timer(_ => DebounceElapsed(), null, Timeout.Infinite, Timeout.Infinite);
|
new Timer(_ => DebounceElapsed(), null, Timeout.Infinite, Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Dispose();
|
Dispose();
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,18 +24,22 @@
|
|||||||
<DependentUpon>appsettings.json</DependentUpon>
|
<DependentUpon>appsettings.json</DependentUpon>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Remove="obj\**" />
|
<Content Remove="obj\**" />
|
||||||
|
<AdditionalFiles Include="..\libhac\src\LibHac\bin\Release\net8.0\LibHac.dll">
|
||||||
|
<Link>LibHac.dll</Link>
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</AdditionalFiles>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- libhac ships a native helper called Ryujinx.HLE.HOS.Native.dll.
|
<!-- libhac ships a native helper called Ryujinx.HLE.HOS.Native.dll.
|
||||||
The build script copies it to the output folder automatically. -->
|
The build script copies it to the output folder automatically. -->
|
||||||
<None Update="..\libhac\src\LibHac\bin\Release\net8.0\LibHac.dll" CopyToOutputDirectory="PreserveNewest" />
|
<None Update="..\Dependencies\LibHac.dll" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<None Remove="obj\**" />
|
<None Remove="obj\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="LibHac">
|
<Reference Include="LibHac">
|
||||||
<HintPath>..\libhac\src\LibHac\bin\Release\net8.0\LibHac.dll</HintPath>
|
<HintPath>..\Dependencies\LibHac.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@
|
|||||||
"SnapshotFile": "index.tfl",
|
"SnapshotFile": "index.tfl",
|
||||||
"SnapshotBackupFile": "snapshot.bin"
|
"SnapshotBackupFile": "snapshot.bin"
|
||||||
},
|
},
|
||||||
|
"IndexBuilder": {
|
||||||
|
"ApiBaseUrl": "http://tinfoil.localhost:80"
|
||||||
|
},
|
||||||
"TitleDb": {
|
"TitleDb": {
|
||||||
"CountryCode": "AU",
|
"CountryCode": "AU",
|
||||||
"Language": "en",
|
"Language": "en",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace TinfoilVibeServerTest.Tests
|
|||||||
_middleware = new BasicAuthMiddleware(_next);
|
_middleware = new BasicAuthMiddleware(_next);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpContext CreateContext(string authHeader = null, string ip = "127.0.0.1", int? uid = null)
|
private HttpContext CreateContext(string authHeader = "", string ip = "127.0.0.1", int? uid = null)
|
||||||
{
|
{
|
||||||
var ctx = new DefaultHttpContext();
|
var ctx = new DefaultHttpContext();
|
||||||
ctx.Connection.RemoteIpAddress = IPAddress.Parse(ip);
|
ctx.Connection.RemoteIpAddress = IPAddress.Parse(ip);
|
||||||
|
|||||||
Reference in New Issue
Block a user