Tons of server-side fixes and tweaks to deal with strange nexus states

This commit is contained in:
Timothy Baldridge 2020-08-11 22:25:12 -06:00
parent 9d3f36559e
commit bc94ec4321
20 changed files with 295 additions and 114 deletions

View File

@ -29,7 +29,8 @@ namespace Wabbajack.CLI
typeof(ForceHealing),
typeof(HashVariants),
typeof(ParseMeta),
typeof(NoPatch)
typeof(NoPatch),
typeof(NexusPermissions)
};
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Threading.Tasks;
using CommandLine;
using Wabbajack.Common;
using Wabbajack.Lib.NexusApi;
namespace Wabbajack.CLI.Verbs
{
[Verb("nexus-permissions", HelpText = "Get the nexus permissions for a mod")]
public class NexusPermissions : AVerb
{
[Option('m', "mod-id", Required = true, HelpText = "Mod Id")]
public long ModId { get; set; } = 0;
[Option('g', "game", Required = true, HelpText = "Game Name")]
public string GameName { get; set; } = "";
protected override async Task<ExitCode> Run()
{
var game = GameRegistry.GetByFuzzyName(GameName).Game;
var p = await HTMLInterface.GetUploadPermissions(game, ModId);
Console.WriteLine($"Game: {game}");
Console.WriteLine($"ModId: {ModId}");
Console.WriteLine($"Permissions: {p}");
return ExitCode.Ok;
}
}
}

View File

@ -36,24 +36,39 @@ namespace Wabbajack.Common
sigStream.Position = 0;
}
public static void Create(Stream oldData, Stream newData, Stream signature, Stream output)
public static void Create(Stream oldData, Stream newData, Stream signature, Stream output, ProgressReporter? reporter = null)
{
CreateSignature(oldData, signature);
var db = new DeltaBuilder {ProgressReporter = reporter};
var db = new DeltaBuilder {ProgressReporter = reporter ?? new ProgressReporter()};
db.BuildDelta(newData, new SignatureReader(signature, reporter), new AggregateCopyOperationsDecorator(new BinaryDeltaWriter(output)));
}
private class ProgressReporter : IProgressReporter
public class ProgressReporter : IProgressReporter
{
private DateTime _lastUpdate = DateTime.UnixEpoch;
private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(100);
private TimeSpan _updateInterval;
private Action<string, Percent> _report;
public ProgressReporter()
{
_updateInterval = TimeSpan.FromMilliseconds(100);
_report = (s, percent) => Utils.Status(s, percent);
}
public ProgressReporter(TimeSpan updateInterval, Action<string, Percent> report)
{
_updateInterval = updateInterval;
_report = report;
}
public void ReportProgress(string operation, long currentPosition, long total)
{
if (DateTime.Now - _lastUpdate < _updateInterval) return;
_lastUpdate = DateTime.Now;
if (currentPosition >= total || total < 1 || currentPosition < 0)
return;
Utils.Status(operation, new Percent(total, currentPosition));
_report(operation, new Percent(total, currentPosition));
}
}

View File

@ -21,7 +21,7 @@ namespace Wabbajack.Lib.Downloaders
private bool _prepared;
private AsyncLock _lock = new AsyncLock();
private UserStatus? _status;
private NexusApiClient? _client;
public INexusApi? Client;
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable("nexusapikey");
@ -115,9 +115,9 @@ namespace Wabbajack.Lib.Downloaders
await CLIArguments.ApiKey.ToEcryptedJson("nexusapikey");
}
_client = await NexusApiClient.Get();
_status = await _client.GetUserStatus();
if (!_client.IsAuthenticated)
Client = await NexusApiClient.Get();
_status = await Client.GetUserStatus();
if (!Client.IsAuthenticated)
{
Utils.ErrorThrow(new UnconvertedError(
$"Authenticating for the Nexus failed. A nexus account is required to automatically download mods."));
@ -205,20 +205,13 @@ namespace Wabbajack.Lib.Downloaders
try
{
var client = await NexusApiClient.Get();
var modInfo = await client.GetModInfo(Game, ModID);
if (!modInfo.available) return false;
var modFiles = await client.GetModFiles(Game, ModID);
var found = modFiles.files
.FirstOrDefault(file => file.file_id == FileID && file.category_name != null);
if (found != null)
return true;
Utils.Log($"Could not validate {URL} with cache, validating manually");
modFiles = await client.GetModFiles(Game, ModID, false);
found = modFiles.files
.FirstOrDefault(file => file.file_id == FileID && file.category_name != null);
return found != null;
}
catch (Exception ex)
@ -244,11 +237,15 @@ namespace Wabbajack.Lib.Downloaders
return new[] {"[General]", $"gameName={Game.MetaData().MO2ArchiveName}", $"modID={ModID}", $"fileID={FileID}"};
}
public static Func<Archive, Task<AbsolutePath>> DownloadShortcut = async a => default;
public async Task<(Archive? Archive, TempFile NewFile)> FindUpgrade(Archive a)
{
var client = await NexusApiClient.Get();
var mod = await client.GetModInfo(Game, ModID);
if (!mod.available)
return default;
var files = await client.GetModFiles(Game, ModID);
var oldFile = files.files.FirstOrDefault(f => f.file_id == FileID);
var nl = new Levenshtein();
@ -265,13 +262,21 @@ namespace Wabbajack.Lib.Downloaders
return default;
}
var tempFile = new TempFile();
var newArchive = new Archive(new State {Game = Game, ModID = ModID, FileID = newFile.file_id})
{
Name = newFile.file_name,
};
var fastPath = await DownloadShortcut(newArchive);
if (fastPath != default)
{
newArchive.Size = fastPath.Size;
newArchive.Hash = await fastPath.FileHashAsync();
return (newArchive, new TempFile());
}
var tempFile = new TempFile();
await newArchive.State.Download(newArchive, tempFile.Path);
newArchive.Size = tempFile.Path.Size;

View File

@ -89,7 +89,7 @@ namespace Wabbajack.Lib.ModListRegistry
try
{
var client = new Http.Client();
return (await client.GetStringAsync(Consts.ModlistMetadataURL)).FromJsonString<List<ModlistMetadata>>();
return (await client.GetStringAsync(Consts.UnlistedModlistMetadataURL)).FromJsonString<List<ModlistMetadata>>();
}
catch (Exception ex)
{

View File

@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
namespace Wabbajack.Lib.NexusApi
{
public interface INexusApi
{
public Task<string> GetNexusDownloadLink(NexusDownloader.State archive);
public Task<NexusApiClient.GetModFilesResponse> GetModFiles(Game game, long modid, bool useCache = true);
public Task<ModInfo> GetModInfo(Game game, long modId, bool useCache = true);
public Task<UserStatus> GetUserStatus();
public Task<bool> IsPremium();
public bool IsAuthenticated { get; }
}
}

View File

@ -16,7 +16,7 @@ using Wabbajack.Lib.WebAutomation;
namespace Wabbajack.Lib.NexusApi
{
public class NexusApiClient : ViewModel
public class NexusApiClient : ViewModel, INexusApi
{
private static readonly string API_KEY_CACHE_FILE = "nexus.key_cache";
@ -24,7 +24,7 @@ namespace Wabbajack.Lib.NexusApi
#region Authentication
public string? ApiKey { get; }
public static string? ApiKey { get; set; }
public bool IsAuthenticated => ApiKey != null;
@ -313,7 +313,11 @@ namespace Wabbajack.Lib.NexusApi
public async Task<string> GetNexusDownloadLink(NexusDownloader.State archive)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var info = await GetModInfo(archive.Game, archive.ModID);
if (!info.available)
throw new Exception("Mod unavailable");
var url = $"https://api.nexusmods.com/v1/games/{archive.Game.MetaData().NexusName}/mods/{archive.ModID}/files/{archive.FileID}/download_link.json";
try
{
@ -321,6 +325,8 @@ namespace Wabbajack.Lib.NexusApi
}
catch (HttpException ex)
{
if (ex.Code != 403 || await IsPremium())
{
throw;
@ -383,20 +389,6 @@ namespace Wabbajack.Lib.NexusApi
public string URI { get; set; } = string.Empty;
}
private static string? _localCacheDir;
public static string LocalCacheDir
{
get
{
if (_localCacheDir == null)
_localCacheDir = Environment.GetEnvironmentVariable("NEXUSCACHEDIR");
if (_localCacheDir == null)
throw new ArgumentNullException($"Enviornment variable could not be located: NEXUSCACHEDIR");
return _localCacheDir;
}
set => _localCacheDir = value;
}
public static Uri ManualDownloadUrl(NexusDownloader.State state)
{
return new Uri($"https://www.nexusmods.com/{state.Game.MetaData().NexusName}/mods/{state.ModID}?tab=files");

View File

@ -152,5 +152,19 @@ namespace Wabbajack.BuildServer.Test
Assert.Equal(ts, (long)ts.AsUnixTime().AsUnixTime());
}
[Fact]
public async Task CanGetAndSetPermissions()
{
var game = Game.Oblivion;
var modId = 4424;
var sql = Fixture.GetService<SqlService>();
foreach (HTMLInterface.PermissionValue result in Enum.GetValues(typeof(HTMLInterface.PermissionValue)))
{
await sql.SetNexusPermission(game, modId, result);
Assert.Equal(result, (await sql.GetNexusPermissions())[(game, modId)]);
}
}
}
}

View File

@ -637,6 +637,7 @@ CREATE TABLE [dbo].[NexusKeys](
[ApiKey] [nvarchar](162) NOT NULL,
[DailyRemain] [int] NOT NULL,
[HourlyRemain] [int] NOT NULL,
[IsPremium] [tinyint] NOT NULL,
CONSTRAINT [PK_NexusKeys] PRIMARY KEY CLUSTERED
(
[ApiKey] ASC

View File

@ -127,7 +127,7 @@ namespace Wabbajack.BuildServer.Controllers
private async Task Log(DateTime timestamp, string action, string subject, string metricsKey = null)
{
_logger.Log(LogLevel.Information, $"Log - {timestamp} {action} {subject} {metricsKey}");
//_logger.Log(LogLevel.Information, $"Log - {timestamp} {action} {subject} {metricsKey}");
await _sql.IngestMetric(new Metric
{
Timestamp = timestamp, Action = action, Subject = subject, MetricsKey = metricsKey

View File

@ -73,15 +73,15 @@ namespace Wabbajack.BuildServer.Controllers
{
if (await request.OldArchive.State.Verify(request.OldArchive))
{
_logger.LogInformation(
$"Refusing to upgrade ({request.OldArchive.State.PrimaryKeyString}), old archive is valid");
//_logger.LogInformation(
// $"Refusing to upgrade ({request.OldArchive.State.PrimaryKeyString}), old archive is valid");
return NotFound("File is Valid");
}
}
catch (Exception ex)
{
_logger.LogInformation(
$"Refusing to upgrade ({request.OldArchive.State.PrimaryKeyString}), due to upgrade failure");
//_logger.LogInformation(
// $"Refusing to upgrade ({request.OldArchive.State.PrimaryKeyString}), due to upgrade failure");
return NotFound("File is Valid");
}
@ -99,13 +99,13 @@ namespace Wabbajack.BuildServer.Controllers
{
if (patch.PatchSize != 0)
{
_logger.Log(LogLevel.Information, $"Upgrade requested from {oldDownload.Archive.Hash} to {newDownload.Archive.Hash} patch Found");
//_logger.Log(LogLevel.Information, $"Upgrade requested from {oldDownload.Archive.Hash} to {newDownload.Archive.Hash} patch Found");
await _sql.MarkPatchUsage(oldDownload.Id, newDownload.Id);
return
Ok(
$"https://{(await _creds).Username}.b-cdn.net/{request.OldArchive.Hash.ToHex()}_{request.NewArchive.Hash.ToHex()}");
}
_logger.Log(LogLevel.Information, $"Upgrade requested from {oldDownload.Archive.Hash} to {newDownload.Archive.Hash} patch found but was failed");
//_logger.Log(LogLevel.Information, $"Upgrade requested from {oldDownload.Archive.Hash} to {newDownload.Archive.Hash} patch found but was failed");
return NotFound("Patch creation failed");
}
@ -119,7 +119,7 @@ namespace Wabbajack.BuildServer.Controllers
await _quickSync.Notify<PatchBuilder>();
}
_logger.Log(LogLevel.Information, $"Upgrade requested from {oldDownload.Archive.Hash} to {newDownload.Archive.Hash} patch found is processing");
//_logger.Log(LogLevel.Information, $"Upgrade requested from {oldDownload.Archive.Hash} to {newDownload.Archive.Hash} patch found is processing");
// Still processing
return Accepted();
}

View File

@ -10,9 +10,11 @@ using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Wabbajack.Common;
using Wabbajack.Common.Exceptions;
using Wabbajack.Lib;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Server.DataLayer;
using Wabbajack.Server.Services;
namespace Wabbajack.BuildServer.Controllers
{
@ -27,12 +29,14 @@ namespace Wabbajack.BuildServer.Controllers
private static long ForwardCount = 0;
private SqlService _sql;
private ILogger<NexusCache> _logger;
private NexusKeyMaintainance _keys;
public NexusCache(ILogger<NexusCache> logger, SqlService sql, AppSettings settings)
public NexusCache(ILogger<NexusCache> logger, SqlService sql, AppSettings settings, NexusKeyMaintainance keys)
{
_settings = settings;
_sql = sql;
_logger = logger;
_keys = keys;
}
/// <summary>
@ -74,7 +78,7 @@ namespace Wabbajack.BuildServer.Controllers
{
var key = Request.Headers["apikey"].FirstOrDefault();
if (key == null)
return await NexusApiClient.Get(null);
return await _keys.GetClient();
if (await _sql.HaveKey(key))
return await NexusApiClient.Get(key);
@ -89,18 +93,31 @@ namespace Wabbajack.BuildServer.Controllers
[Route("{GameName}/mods/{ModId}/files.json")]
public async Task<NexusApiClient.GetModFilesResponse> GetModFiles(string GameName, long ModId)
{
_logger.Log(LogLevel.Information, $"{GameName} {ModId}");
//_logger.Log(LogLevel.Information, $"{GameName} {ModId}");
var game = GameRegistry.GetByFuzzyName(GameName).Game;
var result = await _sql.GetModFiles(game, ModId);
string method = "CACHED";
if (result == null)
{
var api = await NexusApiClient.Get(Request.Headers["apikey"].FirstOrDefault());
result = await api.GetModFiles(game, ModId, false);
var api = await GetClient();
var permission = HTMLInterface.GetUploadPermissions(game, ModId);
try
{
result = await api.GetModFiles(game, ModId, false);
}
catch (HttpException ex)
{
if (ex.Code == 403)
result = new NexusApiClient.GetModFilesResponse {files = new List<NexusFileInfo>()};
else
throw;
}
var date = result.files.Select(f => f.uploaded_time).OrderByDescending(o => o).FirstOrDefault();
date = date == default ? DateTime.UtcNow : date;
await _sql.AddNexusModFiles(game, ModId, date, result);
await _sql.SetNexusPermission(game, ModId, await permission);
method = "NOT_CACHED";
Interlocked.Increment(ref ForwardCount);

View File

@ -83,6 +83,25 @@ namespace Wabbajack.Server.DataLayer
}
public async Task<ArchiveDownload> GetArchiveDownload(string primaryKeyString)
{
await using var conn = await Open();
var result = await conn.QueryFirstOrDefaultAsync<(Guid, long?, Hash?, bool?, AbstractDownloadState, DateTime?)>(
"SELECT Id, Size, Hash, IsFailed, DownloadState, DownloadFinished FROM dbo.ArchiveDownloads WHERE PrimaryKeyString = @PrimaryKeyString AND IsFailed = 0",
new {PrimaryKeyString = primaryKeyString});
if (result == default)
return null;
return new ArchiveDownload
{
Id = result.Item1,
IsFailed = result.Item4,
DownloadFinished = result.Item6,
Archive = new Archive(result.Item5) {Size = result.Item2 ?? 0, Hash = result.Item3 ?? default}
};
}
public async Task<ArchiveDownload> GetArchiveDownload(string primaryKeyString, Hash hash, long size)
{
await using var conn = await Open();

View File

@ -117,6 +117,7 @@ namespace Wabbajack.Server.DataLayer
await using var conn = await Open();
await conn.ExecuteAsync("DELETE FROM dbo.NexusModFiles WHERE ModId = @ModId", new {ModId = modId});
await conn.ExecuteAsync("DELETE FROM dbo.NexusModInfos WHERE ModId = @ModId", new {ModId = modId});
await conn.ExecuteAsync("DELETE FROM dbo.NexusModPermissions WHERE ModId = @ModId", new {ModId = modId});
}
public async Task<Dictionary<(Game, long), HTMLInterface.PermissionValue>> GetNexusPermissions()
@ -128,6 +129,17 @@ namespace Wabbajack.Server.DataLayer
return results.ToDictionary(f => (GameRegistry.ByNexusID[f.Item1], f.Item2),
f => (HTMLInterface.PermissionValue)f.Item3);
}
public async Task<Dictionary<(Game, long), HTMLInterface.PermissionValue>> GetHiddenNexusMods()
{
await using var conn = await Open();
var results =
await conn.QueryAsync<(int, long, int)>("SELECT NexusGameID, ModID, Permissions FROM NexusModPermissions WHERE Permissions = @Permissions",
new {Permissions = (int)HTMLInterface.PermissionValue.Hidden});
return results.ToDictionary(f => (GameRegistry.ByNexusID[f.Item1], f.Item2),
f => (HTMLInterface.PermissionValue)f.Item3);
}
public async Task SetNexusPermissions(IEnumerable<(Game, long, HTMLInterface.PermissionValue)> permissions)
{
@ -166,7 +178,7 @@ namespace Wabbajack.Server.DataLayer
await using var conn = await Open();
var tx = await conn.BeginTransactionAsync();
await conn.ExecuteAsync("DELETE FROM NexusModPermissions WHERE GameID = @GameID AND ModID = @ModID", new
await conn.ExecuteAsync("DELETE FROM NexusModPermissions WHERE NexusGameID = @GameID AND ModID = @ModID", new
{
GameID = game.MetaData().NexusGameId,
ModID = modId

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -46,6 +47,9 @@ namespace Wabbajack.Server.Services
using var queue = new WorkQueue();
var oldSummaries = Summaries;
var stopwatch = new Stopwatch();
stopwatch.Start();
var results = await data.ModLists.PMap(queue, async metadata =>
{
var oldSummary =
@ -54,15 +58,24 @@ namespace Wabbajack.Server.Services
var listArchives = await _sql.ModListArchives(metadata.Links.MachineURL);
var archives = await listArchives.PMap(queue, async archive =>
{
var (_, result) = await ValidateArchive(data, archive);
if (result == ArchiveStatus.InValid)
try
{
if (data.Mirrors.Contains(archive.Hash))
return (archive, ArchiveStatus.Mirrored);
return await TryToHeal(data, archive, metadata);
}
var (_, result) = await ValidateArchive(data, archive);
if (result == ArchiveStatus.InValid)
{
if (data.Mirrors.Contains(archive.Hash))
return (archive, ArchiveStatus.Mirrored);
return await TryToHeal(data, archive, metadata);
}
return (archive, result);
return (archive, result);
}
catch (Exception ex)
{
_logger.LogError(ex, $"During Validation of {archive.Hash} {archive.State.PrimaryKeyString}");
return (archive, ArchiveStatus.InValid);
}
});
var failedCount = archives.Count(f => f.Item2 == ArchiveStatus.InValid);
@ -141,20 +154,16 @@ namespace Wabbajack.Server.Services
return (summary, detailed);
});
Summaries = results;
stopwatch.Stop();
_logger.LogInformation($"Finished Validation in {stopwatch.Elapsed}");
return Summaries.Count(s => s.Summary.HasFailures);
}
private AsyncLock _healLock = new AsyncLock();
private async Task<(Archive, ArchiveStatus)> TryToHeal(ValidationData data, Archive archive, ModlistMetadata modList)
{
using var _ = await _healLock.WaitAsync();
if (!(archive.State is IUpgradingState))
{
_logger.Log(LogLevel.Information, $"Cannot heal {archive.State.PrimaryKeyString} because it's not a healable state");
return (archive, ArchiveStatus.InValid);
}
var srcDownload = await _sql.GetArchiveDownload(archive.State.PrimaryKeyString, archive.Hash, archive.Size);
if (srcDownload == null || srcDownload.IsFailed == true)
{
@ -163,7 +172,7 @@ namespace Wabbajack.Server.Services
}
var patches = await _sql.PatchesForSource(srcDownload.Id);
var patches = await _sql.PatchesForSource(archive.Hash);
foreach (var patch in patches)
{
if (patch.Finished is null)
@ -176,28 +185,80 @@ namespace Wabbajack.Server.Services
if (status == ArchiveStatus.Valid)
return (archive, ArchiveStatus.Updated);
}
using var _ = await _healLock.WaitAsync();
var upgradeTime = DateTime.UtcNow;
_logger.LogInformation($"Validator Finding Upgrade for {archive.Hash} {archive.State.PrimaryKeyString}");
NexusDownloader.State.DownloadShortcut = async findIt =>
{
_logger.LogInformation($"Quick find for {findIt.State.PrimaryKeyString}");
var foundArchive = await _sql.GetArchiveDownload(findIt.State.PrimaryKeyString);
if (foundArchive == null)
{
_logger.LogInformation($"No Quick find for {findIt.State.PrimaryKeyString}");
return default;
}
return _archives.TryGetPath(foundArchive.Archive.Hash, out var path) ? path : default;
};
var upgrade = await (archive.State as IUpgradingState)?.FindUpgrade(archive);
if (upgrade == default)
{
_logger.Log(LogLevel.Information, $"Cannot heal {archive.State.PrimaryKeyString} because an alternative wasn't found");
return (archive, ArchiveStatus.InValid);
}
_logger.LogInformation($"Upgrade {upgrade.Archive.State.PrimaryKeyString} found for {archive.State.PrimaryKeyString}");
await _archives.Ingest(upgrade.NewFile.Path);
var id = await _sql.AddKnownDownload(upgrade.Archive, upgradeTime);
{
}
var found = await _sql.GetArchiveDownload(upgrade.Archive.State.PrimaryKeyString, upgrade.Archive.Hash,
upgrade.Archive.Size);
Guid id;
if (found == null)
{
if (upgrade.NewFile.Path.Exists)
await _archives.Ingest(upgrade.NewFile.Path);
id = await _sql.AddKnownDownload(upgrade.Archive, upgradeTime);
}
else
{
id = found.Id;
}
var destDownload = await _sql.GetArchiveDownload(id);
await _sql.AddPatch(new Patch {Src = srcDownload, Dest = destDownload});
_logger.Log(LogLevel.Information, $"Enqueued Patch from {srcDownload.Archive.Hash} to {destDownload.Archive.Hash}");
await _discord.Send(Channel.Ham, new DiscordMessage { Content = $"Enqueued Patch from {srcDownload.Archive.Hash} to {destDownload.Archive.Hash} to auto-heal `{modList.Links.MachineURL}`" });
if (destDownload.Archive.Hash == srcDownload.Archive.Hash && destDownload.Archive.State.PrimaryKeyString == srcDownload.Archive.State.PrimaryKeyString)
{
_logger.Log(LogLevel.Information, $"Can't heal because src and dest match");
return (archive, ArchiveStatus.InValid);
}
var existing = await _sql.FindPatch(srcDownload.Id, destDownload.Id);
if (existing == null)
{
await _sql.AddPatch(new Patch {Src = srcDownload, Dest = destDownload});
_logger.Log(LogLevel.Information,
$"Enqueued Patch from {srcDownload.Archive.Hash} to {destDownload.Archive.Hash}");
await _discord.Send(Channel.Ham,
new DiscordMessage
{
Content =
$"Enqueued Patch from {srcDownload.Archive.Hash} to {destDownload.Archive.Hash} to auto-heal `{modList.Links.MachineURL}`"
});
}
await upgrade.NewFile.DisposeAsync();
_logger.LogInformation($"Patch in progress {archive.Hash} {archive.State.PrimaryKeyString}");
return (archive, ArchiveStatus.Updating);
}

View File

@ -4,6 +4,7 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Wabbajack.BuildServer;
using Wabbajack.Common;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Server.DataLayer;
@ -12,6 +13,7 @@ namespace Wabbajack.Server.Services
public class NexusKeyMaintainance : AbstractService<NexusKeyMaintainance, int>
{
private SqlService _sql;
private string _selfKey;
public NexusKeyMaintainance(ILogger<NexusKeyMaintainance> logger, AppSettings settings, SqlService sql, QuickSync quickSync) : base(logger, settings, quickSync, TimeSpan.FromHours(4))
{
@ -21,7 +23,7 @@ namespace Wabbajack.Server.Services
public async Task<NexusApiClient> GetClient()
{
var keys = await _sql.GetNexusApiKeysWithCounts(1500);
foreach (var key in keys)
foreach (var key in keys.Where(k => k.Key != _selfKey))
{
return new TrackingClient(_sql, key);
}
@ -31,6 +33,7 @@ namespace Wabbajack.Server.Services
public override async Task<int> Execute()
{
_selfKey ??= await Utils.FromEncryptedJson<string>("nexusapikey");
var keys = await _sql.GetNexusApiKeysWithCounts(0);
_logger.Log(LogLevel.Information, $"Verifying {keys.Count} API Keys");
foreach (var key in keys)
@ -70,7 +73,7 @@ namespace Wabbajack.Server.Services
HourlyRemaining = key.Hourly;
}
protected virtual async Task UpdateRemaining(HttpResponseMessage response)
protected override async Task UpdateRemaining(HttpResponseMessage response)
{
await base.UpdateRemaining(response);
try

View File

@ -17,9 +17,7 @@ namespace Wabbajack.Server.Services
private DiscordWebHook _discord;
private SqlService _sql;
public static TimeSpan MaxSync = TimeSpan.FromHours(4);
public NexusPermissionsUpdater(ILogger<NexusKeyMaintainance> logger, AppSettings settings, QuickSync quickSync, DiscordWebHook discord, SqlService sql) : base(logger, settings, quickSync, TimeSpan.FromSeconds(1))
public NexusPermissionsUpdater(ILogger<NexusKeyMaintainance> logger, AppSettings settings, QuickSync quickSync, DiscordWebHook discord, SqlService sql) : base(logger, settings, quickSync, TimeSpan.FromMinutes(5))
{
_discord = discord;
_sql = sql;
@ -32,38 +30,33 @@ namespace Wabbajack.Server.Services
var data = await _sql.ModListArchives();
var nexusArchives = data.Select(a => a.State).OfType<NexusDownloader.State>().Select(d => (d.Game, d.ModID))
.Where(g => g.Game.MetaData().NexusGameId != 0)
.Distinct()
.ToList();
_logger.LogInformation($"Starting nexus permissions updates for {nexusArchives.Count} mods");
using var queue = new WorkQueue(1);
using var queue = new WorkQueue();
var prev = await _sql.GetNexusPermissions();
var prev = await _sql.GetHiddenNexusMods();
_logger.LogInformation($"Found {prev.Count} hidden nexus mods to check");
var lag = MaxSync / nexusArchives.Count * 2;
await nexusArchives.PMap(queue, async archive =>
await prev.PMap(queue, async archive =>
{
_logger.LogInformation($"Checking permissions for {archive.Game} {archive.ModID}");
var result = await HTMLInterface.GetUploadPermissions(archive.Game, archive.ModID);
await _sql.SetNexusPermission(archive.Game, archive.ModID, result);
var (game, modID) = archive.Key;
_logger.LogInformation($"Checking permissions for {game} {modID}");
var result = await HTMLInterface.GetUploadPermissions(game, modID);
await _sql.SetNexusPermission(game, modID, result);
if (prev.TryGetValue((archive.Game, archive.ModID), out var oldPermission))
if (archive.Value != result)
{
if (oldPermission != result)
{
await _discord.Send(Channel.Spam,
new DiscordMessage {
Content = $"Permissions status of {archive.Game} {archive.ModID} was {oldPermission} is now {result}"
});
await _sql.PurgeNexusCache(archive.ModID);
await _quickSync.Notify<ListValidator>();
}
await _discord.Send(Channel.Ham,
new DiscordMessage {
Content = $"Permissions status of {game} {modID} was {archive.Value} is now {result}"
});
await _sql.PurgeNexusCache(modID);
await _quickSync.Notify<ListValidator>();
}
await Task.Delay(lag);
});
return 1;

View File

@ -18,13 +18,15 @@ namespace Wabbajack.Server.Services
private AppSettings _settings;
private GlobalInformation _globalInformation;
private ILogger<NexusPoll> _logger;
private NexusKeyMaintainance _keys;
public NexusPoll(ILogger<NexusPoll> logger, AppSettings settings, SqlService service, GlobalInformation globalInformation)
public NexusPoll(ILogger<NexusPoll> logger, AppSettings settings, SqlService service, GlobalInformation globalInformation, NexusKeyMaintainance keys)
{
_sql = service;
_settings = settings;
_globalInformation = globalInformation;
_logger = logger;
_keys = keys;
}
public async Task UpdateNexusCacheRSS()
@ -67,7 +69,7 @@ namespace Wabbajack.Server.Services
{
using var _ = _logger.BeginScope("Nexus Update via API");
_logger.Log(LogLevel.Information, "Starting Nexus Update via API");
var api = await NexusApiClient.Get();
var api = await _keys.GetClient();
var gameTasks = GameRegistry.Games.Values
.Where(game => game.NexusName != null)

View File

@ -55,7 +55,7 @@ namespace Wabbajack.Server.Services
$"Building patch from {patch.Src.Archive.State.PrimaryKeyString} to {patch.Dest.Archive.State.PrimaryKeyString}"
});
if (patch.Src.Archive.Hash == patch.Dest.Archive.Hash)
if (patch.Src.Archive.Hash == patch.Dest.Archive.Hash && patch.Src.Archive.State.PrimaryKeyString == patch.Dest.Archive.State.PrimaryKeyString)
{
await patch.Fail(_sql, "Hashes match");
continue;
@ -76,7 +76,7 @@ namespace Wabbajack.Server.Services
await using var destStream = await destPath.OpenShared();
await using var sigStream = await sigFile.Path.Create();
await using var patchOutput = await patchFile.Path.Create();
OctoDiff.Create(destStream, srcStream, sigStream, patchOutput);
OctoDiff.Create(destStream, srcStream, sigStream, patchOutput, new OctoDiff.ProgressReporter(TimeSpan.FromSeconds(1), (s, p) => _logger.LogInformation($"Patch Builder: {p} {s}")));
await patchOutput.DisposeAsync();
var size = patchFile.Path.Size;

View File

@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Wabbajack.Common;
namespace Wabbajack.Server.Services
@ -11,6 +12,12 @@ namespace Wabbajack.Server.Services
{
private Dictionary<Type, CancellationTokenSource> _syncs = new Dictionary<Type, CancellationTokenSource>();
private AsyncLock _lock = new AsyncLock();
private ILogger<QuickSync> _logger;
public QuickSync(ILogger<QuickSync> logger)
{
_logger = logger;
}
public async Task<CancellationToken> GetToken<T>()
{
@ -36,18 +43,13 @@ namespace Wabbajack.Server.Services
public async Task Notify<T>()
{
_logger.LogInformation($"Quicksync {typeof(T).Name}");
// Needs debugging
/*
using var _ = await _lock.WaitAsync();
if (_syncs.TryGetValue(typeof(T), out var ct))
{
ct.Cancel();
}
*/
}
}
}