Several server-side changes to make the system more durable to API call limits.

This commit is contained in:
Timothy Baldridge 2020-05-16 09:08:40 -06:00
parent 29d327d747
commit 88e3db0b2a
17 changed files with 322 additions and 130 deletions

View File

@ -1,5 +1,8 @@
### Changelog
#### Version - 2.0.5.1 - 5/16/2020
* Close automatically Wabbajack when opening the CLI, resolves the "RocksDB is in use" errors
#### Version - 2.0.5.0 - 5/14/2020
* Make the CDN downloads multi-threaded
* Optimize installation of included files

View File

@ -292,11 +292,18 @@ namespace Wabbajack.Lib
public async Task HashArchives()
{
var hashResults = await
DownloadFolder.EnumerateFiles()
Utils.Log("Looking for files to hash");
var toHash = DownloadFolder.EnumerateFiles()
.Concat(Game.GameLocation().EnumerateFiles())
.Where(e => e.Extension != Consts.HashFileExtension)
.PMap(Queue, async e => (await e.FileHashCachedAsync(), e));
.ToList();
Utils.Log($"Found {toHash} files to hash");
var hashResults = await
toHash
.PMap(Queue, async e => (await e.FileHashCachedAsync(), e));
HashedArchives.SetTo(hashResults
.OrderByDescending(e => e.Item2.LastModified)
.GroupBy(e => e.Item1)

View File

@ -121,7 +121,8 @@ namespace Wabbajack.Lib.Downloaders
$"Authenticating for the Nexus failed. A nexus account is required to automatically download mods."));
return;
}
await Metrics.Send("nexus_login", _client.ApiKey!);
if (!await _client.IsPremium())
{

View File

@ -157,8 +157,11 @@ namespace Wabbajack.Lib.NexusApi
{
var url = "https://api.nexusmods.com/v1/users/validate.json";
using var response = await HttpClient.GetAsync(url);
return (int.Parse(response.Headers.GetValues("X-RL-Daily-Remaining").First()),
var result = (int.Parse(response.Headers.GetValues("X-RL-Daily-Remaining").First()),
int.Parse(response.Headers.GetValues("X-RL-Hourly-Remaining").First()));
_dailyRemaining = result.Item1;
_hourlyRemaining = result.Item2;
return result;
}
#endregion
@ -177,6 +180,13 @@ namespace Wabbajack.Lib.NexusApi
return _dailyRemaining;
}
}
protected set
{
lock (RemainingLock)
{
_dailyRemaining = value;
}
}
}
private int _hourlyRemaining;
@ -189,10 +199,18 @@ namespace Wabbajack.Lib.NexusApi
return _hourlyRemaining;
}
}
protected set
{
lock (RemainingLock)
{
_hourlyRemaining = value;
}
}
}
private void UpdateRemaining(HttpResponseMessage response)
protected virtual async Task UpdateRemaining(HttpResponseMessage response)
{
try
{
@ -221,7 +239,7 @@ namespace Wabbajack.Lib.NexusApi
#endregion
private NexusApiClient(string? apiKey = null)
protected NexusApiClient(string? apiKey = null)
{
ApiKey = apiKey;
@ -250,7 +268,7 @@ namespace Wabbajack.Lib.NexusApi
try
{
using var response = await HttpClient.GetAsync(url);
UpdateRemaining(response);
await UpdateRemaining(response);
if (!response.IsSuccessStatusCode)
{
Utils.Log($"Nexus call failed: {response.RequestMessage.RequestUri}");

View File

@ -60,7 +60,7 @@ namespace Wabbajack.BuildServer.Test
}
[Fact]
public async Task CanQueryAndFindNexusModfilesSlow()
public async Task CanQueryAndFindNexusModfilesFast()
{
var startTime = DateTime.UtcNow;
var sql = Fixture.GetService<SqlService>();
@ -68,8 +68,7 @@ namespace Wabbajack.BuildServer.Test
await sql.DeleteNexusModFilesUpdatedBeforeDate(Game.SkyrimSpecialEdition, 1137, DateTime.UtcNow);
await sql.DeleteNexusModInfosUpdatedBeforeDate(Game.SkyrimSpecialEdition, 1137, DateTime.UtcNow);
var result = await validator.SlowNexusModStats(new ValidationData(),
new NexusDownloader.State {Game = Game.SkyrimSpecialEdition, ModID = 1137, FileID = 121449});
var result = await validator.FastNexusModStats(new NexusDownloader.State {Game = Game.SkyrimSpecialEdition, ModID = 1137, FileID = 121449});
Assert.Equal(ArchiveStatus.Valid, result);
var gameId = Game.SkyrimSpecialEdition.MetaData().NexusGameId;
@ -119,6 +118,13 @@ namespace Wabbajack.BuildServer.Test
Assert.Equal(b, new Box{Value = b}.ToJson().FromJsonString<Box>().Value);
Assert.NotEqual(a.Hour, b.Hour);
Assert.Equal(b.Hour, new Box{Value = a}.ToJson().FromJsonString<Box>().Value.Hour);
var ts = (long)1589528640;
var ds = DateTime.Parse("2020-05-15 07:44:00.000");
Assert.Equal(ds, ts.AsUnixTime());
Assert.Equal(ts, (long)ds.AsUnixTime());
Assert.Equal(ts, (long)ts.AsUnixTime().AsUnixTime());
}
}

View File

@ -551,6 +551,24 @@ CONSTRAINT [PK_NexusModFilesSlow] PRIMARY KEY CLUSTERED
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[NexusKeys] Script Date: 5/15/2020 5:20:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[NexusKeys](
[ApiKey] [nvarchar](162) NOT NULL,
[DailyRemain] [int] NOT NULL,
[HourlyRemain] [int] NOT NULL,
CONSTRAINT [PK_NexusKeys] PRIMARY KEY CLUSTERED
(
[ApiKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: StoredProcedure [dbo].[MergeAllFilesInArchive] Script Date: 3/28/2020 4:58:59 PM ******/
SET ANSI_NULLS ON
GO

View File

@ -25,6 +25,9 @@ namespace Wabbajack.BuildServer
public bool RunFrontEndJobs { get; set; }
public bool RunBackEndJobs { get; set; }
public bool RunNexusPolling { get; set; }
public bool RunDownloader { get; set; }
public string BunnyCDN_StorageZone { get; set; }
public string SqlConnection { get; set; }

View File

@ -52,7 +52,7 @@ namespace Wabbajack.BuildServer.Controllers
string method = "CACHED";
if (result == null)
{
var api = await NexusApiClient.Get(Request.Headers["apikey"].FirstOrDefault());
var api = await GetClient();
result = await api.GetModInfo(game, ModId, false);
await _sql.AddNexusModInfo(game, ModId, result.updated_time, result);
@ -69,6 +69,21 @@ namespace Wabbajack.BuildServer.Controllers
return result;
}
private async Task<NexusApiClient> GetClient()
{
var key = Request.Headers["apikey"].FirstOrDefault();
if (key == null)
return await NexusApiClient.Get(null);
if (await _sql.HaveKey(key))
return await NexusApiClient.Get(key);
var client = await NexusApiClient.Get(key);
var (daily, hourly) = await client.GetRemainingApiCalls();
await _sql.SetNexusAPIKey(key, daily, hourly);
return client;
}
[HttpGet]
[Route("{GameName}/mods/{ModId}/files.json")]
public async Task<NexusApiClient.GetModFilesResponse> GetModFiles(string GameName, long ModId)

View File

@ -30,11 +30,6 @@ namespace Wabbajack.Server.DataLayer
@"DELETE FROM dbo.NexusModFiles WHERE Game = @Game AND ModID = @ModId AND LastChecked < @Date
SELECT @@ROWCOUNT AS Deleted",
new {Game = game.MetaData().NexusGameId, ModId = modId, Date = date});
deleted += await conn.ExecuteScalarAsync<long>(
@"DELETE FROM dbo.NexusModFilesSlow WHERE GameId = @Game AND ModID = @ModId AND LastChecked < @Date
SELECT @@ROWCOUNT AS Deleted",
new {Game = game.MetaData().NexusGameId, ModId = modId, Date = date});
return deleted;
}

View File

@ -0,0 +1,51 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
namespace Wabbajack.Server.DataLayer
{
public partial class SqlService
{
public async Task SetNexusAPIKey(string key, long daily, long hourly)
{
await using var conn = await Open();
await using var trans = await conn.BeginTransactionAsync();
await conn.ExecuteAsync(@"DELETE FROM NexusKeys WHERE ApiKey = @ApiKey", new {ApiKey = key}, trans);
await conn.ExecuteAsync(@"INSERT INTO NexusKeys (ApiKey, DailyRemain, HourlyRemain) VALUES (@ApiKey, @DailyRemain, @HourlyRemain)",
new {ApiKey = key, DailyRemain = daily, HourlyRemain = hourly}, trans);
await trans.CommitAsync();
}
public async Task DeleteNexusAPIKey(string key)
{
await using var conn = await Open();
await conn.ExecuteAsync(@"DELETE FROM NexusKeys WHERE ApiKey = @ApiKey", new {ApiKey = key});
}
public async Task<List<string>> GetNexusApiKeys(int threshold = 1500)
{
await using var conn = await Open();
return (await conn.QueryAsync<string>(@"SELECT ApiKey FROM NexusKeys WHERE DailyRemain >= @Threshold ORDER BY DailyRemain DESC",
new {Threshold = threshold})).ToList();
}
public async Task<List<(string Key, int Daily, int Hourly)>> GetNexusApiKeysWithCounts(int threshold = 1500)
{
await using var conn = await Open();
return (await conn.QueryAsync<(string, int, int)>(@"SELECT ApiKey, DailyRemain, HourlyRemain FROM NexusKeys WHERE DailyRemain >= @Threshold ORDER BY DailyRemain DESC",
new {Threshold = threshold})).ToList();
}
public async Task<bool> HaveKey(string key)
{
await using var conn = await Open();
return (await conn.QueryAsync<string>(@"SELECT ApiKey FROM NexusKeys WHERE ApiKey = @ApiKey",
new {ApiKey = key})).Any();
}
}
}

View File

@ -31,8 +31,8 @@ namespace Wabbajack.Server.Services
while (true)
{
var (daily, hourly) = await _nexusClient.GetRemainingApiCalls();
//bool ignoreNexus = hourly < 25;
var ignoreNexus = true;
bool ignoreNexus = (daily < 100 && hourly < 10);
//var ignoreNexus = true;
if (ignoreNexus)
_logger.LogWarning($"Ignoring Nexus Downloads due to low hourly api limit (Daily: {daily}, Hourly:{hourly})");
else
@ -89,6 +89,7 @@ namespace Wabbajack.Server.Services
}
catch (Exception ex)
{
_logger.Log(LogLevel.Warning, $"Error downloading {nextDownload.Archive.State.PrimaryKeyString}");
await nextDownload.Fail(_sql, ex.ToString());
}

View File

@ -22,22 +22,25 @@ namespace Wabbajack.Server.Services
public class ListValidator : AbstractService<ListValidator, int>
{
private SqlService _sql;
private NexusApiClient _nexusClient;
private DiscordWebHook _discord;
private NexusKeyMaintainance _nexus;
public IEnumerable<(ModListSummary Summary, DetailedStatus Detailed)> Summaries { get; private set; } =
new (ModListSummary Summary, DetailedStatus Detailed)[0];
public ListValidator(ILogger<ListValidator> logger, AppSettings settings, SqlService sql)
public ListValidator(ILogger<ListValidator> logger, AppSettings settings, SqlService sql, DiscordWebHook discord, NexusKeyMaintainance nexus)
: base(logger, settings, TimeSpan.FromMinutes(10))
{
_sql = sql;
_discord = discord;
_nexus = nexus;
}
public override async Task<int> Execute()
{
var data = await _sql.GetValidationData();
using var queue = new WorkQueue();
var results = await data.ModLists.PMap(queue, async list =>
@ -94,7 +97,7 @@ namespace Wabbajack.Server.Services
nexusState.Game.MetaData().NexusGameId, nexusState.ModID, nexusState.FileID)):
return (archive, ArchiveStatus.Valid);
case NexusDownloader.State ns:
return (archive, await SlowNexusModStats(data, ns));
return (archive, await FastNexusModStats(ns));
case ManualDownloader.State _:
return (archive, ArchiveStatus.Valid);
default:
@ -109,125 +112,88 @@ namespace Wabbajack.Server.Services
}
}
}
private readonly AsyncLock _slowQueryLock = new AsyncLock();
public async Task<ArchiveStatus> SlowNexusModStats(ValidationData data, NexusDownloader.State ns)
private AsyncLock _lock = new AsyncLock();
public async Task<ArchiveStatus> FastNexusModStats(NexusDownloader.State ns)
{
var gameId = ns.Game.MetaData().NexusGameId;
//using var _ = await _slowQueryLock.WaitAsync();
_logger.Log(LogLevel.Warning, $"Slow querying for {ns.Game} {ns.ModID} {ns.FileID}");
if (data.NexusFiles.Contains((gameId, ns.ModID, ns.FileID)))
return ArchiveStatus.Valid;
if (data.NexusFiles.Contains((gameId, ns.ModID, -1)))
return ArchiveStatus.InValid;
if (data.SlowQueriedFor.Contains((ns.Game, ns.ModID)))
return ArchiveStatus.InValid;
var queryTime = DateTime.UtcNow;
var regex_id = new Regex("(?<=[?;&]id\\=)\\d+");
var regex_file_id = new Regex("(?<=[?;&]file_id\\=)\\d+");
var client = new Common.Http.Client();
var result =
await client.GetHtmlAsync(
$"https://www.nexusmods.com/{ns.Game.MetaData().NexusName}/mods/{ns.ModID}/?tab=files");
var fileIds = result.DocumentNode.Descendants()
.Select(f => f.GetAttributeValue("href", ""))
.Select(f =>
{
var match = regex_id.Match(f);
if (match.Success)
return match.Value;
match = regex_file_id.Match(f);
if (match.Success)
return match.Value;
return null;
})
.Where(m => m != null)
.Select(m => long.Parse(m))
.Distinct()
.ToList();
_logger.Log(LogLevel.Warning, $"Slow queried {fileIds.Count} files");
foreach (var id in fileIds)
{
await _sql.AddNexusModFileSlow(ns.Game, ns.ModID, id, queryTime);
data.NexusFiles.Add((gameId, ns.ModID, id));
}
// Add in the default marker
await _sql.AddNexusModFileSlow(ns.Game, ns.ModID, -1, queryTime);
data.NexusFiles.Add((gameId, ns.ModID, -1));
return fileIds.Contains(ns.FileID) ? ArchiveStatus.Valid : ArchiveStatus.InValid;
}
private async Task<ArchiveStatus> FastNexusModStats(NexusDownloader.State ns)
{
// Check if some other thread has added them
var mod = await _sql.GetNexusModInfoString(ns.Game, ns.ModID);
var files = await _sql.GetModFiles(ns.Game, ns.ModID);
try
if (mod == null || files == null)
{
if (mod == null)
// Aquire the lock
using var lck = await _lock.WaitAsync();
// Check again
mod = await _sql.GetNexusModInfoString(ns.Game, ns.ModID);
files = await _sql.GetModFiles(ns.Game, ns.ModID);
if (mod == null || files == null)
{
_nexusClient ??= await NexusApiClient.Get();
_logger.Log(LogLevel.Information, $"Found missing Nexus mod info {ns.Game} {ns.ModID}");
NexusApiClient nexusClient = await _nexus.GetClient();
var queryTime = DateTime.UtcNow;
try
{
mod = await _nexusClient.GetModInfo(ns.Game, ns.ModID, false);
}
catch
{
mod = new ModInfo
if (mod == null)
{
mod_id = ns.ModID.ToString(), game_id = ns.Game.MetaData().NexusGameId, available = false
};
}
_logger.Log(LogLevel.Information, $"Found missing Nexus mod info {ns.Game} {ns.ModID}");
try
{
mod = await nexusClient.GetModInfo(ns.Game, ns.ModID, false);
}
catch
{
mod = new ModInfo
{
mod_id = ns.ModID.ToString(),
game_id = ns.Game.MetaData().NexusGameId,
available = false
};
}
try
{
await _sql.AddNexusModInfo(ns.Game, ns.ModID, mod.updated_time, mod);
}
catch (Exception _)
{
// Could be a PK constraint failure
}
try
{
await _sql.AddNexusModInfo(ns.Game, ns.ModID, queryTime, mod);
}
catch (Exception _)
{
// Could be a PK constraint failure
}
}
}
if (files == null)
{
_logger.Log(LogLevel.Information, $"Found missing Nexus mod info {ns.Game} {ns.ModID}");
try
{
files = await _nexusClient.GetModFiles(ns.Game, ns.ModID, false);
}
catch
{
files = new NexusApiClient.GetModFilesResponse {files = new List<NexusFileInfo>()};
}
if (files == null)
{
_logger.Log(LogLevel.Information, $"Found missing Nexus mod info {ns.Game} {ns.ModID}");
try
{
files = await nexusClient.GetModFiles(ns.Game, ns.ModID, false);
}
catch
{
files = new NexusApiClient.GetModFilesResponse {files = new List<NexusFileInfo>()};
}
try
{
await _sql.AddNexusModFiles(ns.Game, ns.ModID, mod.updated_time, files);
try
{
await _sql.AddNexusModFiles(ns.Game, ns.ModID, queryTime, files);
}
catch (Exception _)
{
// Could be a PK constraint failure
}
}
}
catch (Exception _)
catch (Exception ex)
{
// Could be a PK constraint failure
return ArchiveStatus.InValid;
}
}
}
catch (Exception ex)
{
return ArchiveStatus.InValid;
}
if (mod.available && files.files.Any(f => !string.IsNullOrEmpty(f.category_name) && f.file_id == ns.FileID))
return ArchiveStatus.Valid;

View File

@ -11,6 +11,7 @@ using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.ModListRegistry;
using Wabbajack.Server.DataLayer;
using Wabbajack.Server.DTOs;
namespace Wabbajack.Server.Services
{
@ -20,13 +21,15 @@ namespace Wabbajack.Server.Services
private AppSettings _settings;
private ArchiveMaintainer _maintainer;
private SqlService _sql;
private DiscordWebHook _discord;
public ModListDownloader(ILogger<ModListDownloader> logger, AppSettings settings, ArchiveMaintainer maintainer, SqlService sql)
public ModListDownloader(ILogger<ModListDownloader> logger, AppSettings settings, ArchiveMaintainer maintainer, SqlService sql, DiscordWebHook discord)
{
_logger = logger;
_settings = settings;
_maintainer = maintainer;
_sql = sql;
_discord = discord;
}
public void Start()
@ -69,6 +72,8 @@ namespace Wabbajack.Server.Services
if (!_maintainer.HaveArchive(list.DownloadMetadata!.Hash))
{
_logger.Log(LogLevel.Information, $"Downloading {list.Links.MachineURL}");
await _discord.Send(Channel.Ham,
new DiscordMessage {Content = $"Downloading {list.Links.MachineURL} - {list.DownloadMetadata.Hash}"});
var tf = new TempFile();
var state = DownloadDispatcher.ResolveArchive(list.Links.Download);
if (state == null)
@ -100,7 +105,9 @@ namespace Wabbajack.Server.Services
{
if (entry == null)
{
Utils.Log($"Bad Modlist {list.Links.MachineURL}");
_logger.LogWarning($"Bad Modlist {list.Links.MachineURL}");
await _discord.Send(Channel.Ham,
new DiscordMessage {Content = $"Bad Modlist {list.Links.MachineURL} - {list.DownloadMetadata.Hash}"});
continue;
}
@ -110,7 +117,9 @@ namespace Wabbajack.Server.Services
}
catch (JsonReaderException ex)
{
Utils.Log($"Bad JSON format for {list.Links.MachineURL}");
_logger.LogWarning($"Bad Modlist {list.Links.MachineURL}");
await _discord.Send(Channel.Ham,
new DiscordMessage {Content = $"Bad Modlist {list.Links.MachineURL} - {list.DownloadMetadata.Hash}"});
continue;
}
}
@ -120,12 +129,20 @@ namespace Wabbajack.Server.Services
catch (Exception ex)
{
_logger.LogError(ex, $"Error downloading modlist {list.Links.MachineURL}");
await _discord.Send(Channel.Ham,
new DiscordMessage {Content = $"Error downloading modlist {list.Links.MachineURL} - {list.DownloadMetadata.Hash}"});
}
}
_logger.Log(LogLevel.Information, $"Done checking modlists. Downloaded {downloaded} new lists");
if (downloaded > 0)
await _discord.Send(Channel.Ham,
new DiscordMessage {Content = $"Downloaded {downloaded} new lists"});
var fc = await _sql.EnqueueModListFilesForIndexing();
_logger.Log(LogLevel.Information, $"Enqueing {fc} files for downloading");
if (fc > 0)
await _discord.Send(Channel.Ham,
new DiscordMessage {Content = $"Enqueing {fc} files for downloading"});
return downloaded;
}

View File

@ -0,0 +1,87 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Wabbajack.BuildServer;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Server.DataLayer;
namespace Wabbajack.Server.Services
{
public class NexusKeyMaintainance : AbstractService<NexusKeyMaintainance, int>
{
private SqlService _sql;
public NexusKeyMaintainance(ILogger<NexusKeyMaintainance> logger, AppSettings settings, SqlService sql) : base(logger, settings, TimeSpan.FromHours(1))
{
_sql = sql;
}
public async Task<NexusApiClient> GetClient()
{
var keys = await _sql.GetNexusApiKeysWithCounts(1500);
foreach (var key in keys)
{
return new TrackingClient(_sql, key);
}
return await NexusApiClient.Get();
}
public override async Task<int> Execute()
{
var keys = await _sql.GetNexusApiKeysWithCounts(0);
_logger.Log(LogLevel.Information, $"Verifying {keys.Count} API Keys");
foreach (var key in keys)
{
try
{
var client = new TrackingClient(_sql, key);
var status = await client.GetUserStatus();
if (!status.is_premium)
{
await _sql.DeleteNexusAPIKey(key.Key);
continue;
}
var (daily, hourly) = await client.GetRemainingApiCalls();
await _sql.SetNexusAPIKey(key.Key, daily, hourly);
}
catch (Exception ex)
{
_logger.Log(LogLevel.Warning, "Update error, purging API key");
await _sql.DeleteNexusAPIKey(key.Key);
}
}
return keys.Count;
}
}
public class TrackingClient : NexusApiClient
{
private SqlService _sql;
public TrackingClient(SqlService sql, (string Key, int Daily, int Hourly) key) : base(key.Key)
{
_sql = sql;
DailyRemaining = key.Daily;
HourlyRemaining = key.Hourly;
}
protected virtual async Task UpdateRemaining(HttpResponseMessage response)
{
await base.UpdateRemaining(response);
try
{
var dailyRemaining = int.Parse(response.Headers.GetValues("x-rl-daily-remaining").First());
var hourlyRemaining = int.Parse(response.Headers.GetValues("x-rl-hourly-remaining").First());
await _sql.SetNexusAPIKey(ApiKey, dailyRemaining, hourlyRemaining);
}
catch (Exception ex)
{
}
}
}
}

View File

@ -98,14 +98,15 @@ namespace Wabbajack.Server.Services
// Mod activity could hide files
var b = d.mod.LastestModActivity.AsUnixTime();
return new {Game = d.game.Game, Date = (a > b ? a : b), ModId = d.mod.ModId};
return new {Game = d.game.Game, Date = (a > b) ? a : b, ModId = d.mod.ModId};
});
var purged = await collected.PMap(queue, async t =>
{
var resultA = await _sql.DeleteNexusModInfosUpdatedBeforeDate(t.Game, t.ModId, t.Date);
var resultB = await _sql.DeleteNexusModFilesUpdatedBeforeDate(t.Game, t.ModId, t.Date);
return resultA + resultB;
long purgeCount = 0;
purgeCount += await _sql.DeleteNexusModInfosUpdatedBeforeDate(t.Game, t.ModId, t.Date);
purgeCount += await _sql.DeleteNexusModFilesUpdatedBeforeDate(t.Game, t.ModId, t.Date);
return purgeCount;
});
_logger.Log(LogLevel.Information, $"Purged {purged.Sum()} cache entries");

View File

@ -64,6 +64,7 @@ namespace Wabbajack.Server
services.AddSingleton<ListValidator>();
services.AddSingleton<ArchiveDownloader>();
services.AddSingleton<DiscordWebHook>();
services.AddSingleton<NexusKeyMaintainance>();
services.AddMvc();
services.AddControllers()
@ -114,6 +115,7 @@ namespace Wabbajack.Server
app.UseService<ListValidator>();
app.UseService<ArchiveDownloader>();
app.UseService<DiscordWebHook>();
app.UseService<NexusKeyMaintainance>();
app.Use(next =>
{

View File

@ -155,6 +155,7 @@ namespace Wabbajack
WorkingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)
};
Process.Start(process);
ShutdownApplication();
}
private static bool IsStartingFromModlist(out AbsolutePath modlistPath)