mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
WIP autoheal
This commit is contained in:
parent
a03cda8a31
commit
2a8021c6f9
@ -1,12 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Wabbajack.Common;
|
using Wabbajack.Common;
|
||||||
using Wabbajack.Common.Http;
|
using Wabbajack.Common.Http;
|
||||||
using Wabbajack.Common.StatusFeed;
|
using Wabbajack.Common.StatusFeed;
|
||||||
|
using Wabbajack.Lib;
|
||||||
|
using Wabbajack.Lib.Downloaders;
|
||||||
using Wabbajack.Lib.FileUploader;
|
using Wabbajack.Lib.FileUploader;
|
||||||
|
using Wabbajack.Lib.ModListRegistry;
|
||||||
using Wabbajack.Server;
|
using Wabbajack.Server;
|
||||||
using Wabbajack.Server.DataLayer;
|
using Wabbajack.Server.DataLayer;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -182,5 +188,79 @@ namespace Wabbajack.BuildServer.Test
|
|||||||
_unsubErr.Dispose();
|
_unsubErr.Dispose();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async Task<Uri> MakeModList()
|
||||||
|
{
|
||||||
|
var archive_data = Encoding.UTF8.GetBytes("Cheese for Everyone!");
|
||||||
|
var test_archive_path = "test_archive.txt".RelativeTo(Fixture.ServerPublicFolder);
|
||||||
|
await test_archive_path.WriteAllBytesAsync(archive_data);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ModListData = new ModList();
|
||||||
|
ModListData.Archives.Add(
|
||||||
|
new Archive(new HTTPDownloader.State(MakeURL("test_archive.txt")))
|
||||||
|
{
|
||||||
|
Hash = await test_archive_path.FileHashAsync(),
|
||||||
|
Name = "test_archive",
|
||||||
|
Size = test_archive_path.Size,
|
||||||
|
});
|
||||||
|
|
||||||
|
var modListPath = "test_modlist.wabbajack".RelativeTo(Fixture.ServerPublicFolder);
|
||||||
|
|
||||||
|
await using (var fs = modListPath.Create())
|
||||||
|
{
|
||||||
|
using var za = new ZipArchive(fs, ZipArchiveMode.Create);
|
||||||
|
var entry = za.CreateEntry("modlist");
|
||||||
|
await using var es = entry.Open();
|
||||||
|
ModListData.ToJson(es);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModListMetaData = new List<ModlistMetadata>
|
||||||
|
{
|
||||||
|
new ModlistMetadata
|
||||||
|
{
|
||||||
|
Official = false,
|
||||||
|
Author = "Test Suite",
|
||||||
|
Description = "A test",
|
||||||
|
DownloadMetadata = new DownloadMetadata
|
||||||
|
{
|
||||||
|
Hash = await modListPath.FileHashAsync(),
|
||||||
|
Size = modListPath.Size
|
||||||
|
},
|
||||||
|
Links = new ModlistMetadata.LinksObject
|
||||||
|
{
|
||||||
|
MachineURL = "test_list",
|
||||||
|
Download = MakeURL("test_modlist.wabbajack")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ModlistMetadata
|
||||||
|
{
|
||||||
|
Official = true,
|
||||||
|
Author = "Test Suite",
|
||||||
|
Description = "A list with a broken hash",
|
||||||
|
DownloadMetadata = new DownloadMetadata()
|
||||||
|
{
|
||||||
|
Hash = Hash.FromLong(42),
|
||||||
|
Size = 42
|
||||||
|
},
|
||||||
|
Links = new ModlistMetadata.LinksObject
|
||||||
|
{
|
||||||
|
MachineURL = "broken_list",
|
||||||
|
Download = MakeURL("test_modlist.wabbajack")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var metadataPath = "test_mod_list_metadata.json".RelativeTo(Fixture.ServerPublicFolder);
|
||||||
|
|
||||||
|
ModListMetaData.ToJson(metadataPath);
|
||||||
|
|
||||||
|
return new Uri(MakeURL("test_mod_list_metadata.json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModList ModListData { get; set; }
|
||||||
|
|
||||||
|
public List<ModlistMetadata> ModListMetaData { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,79 +135,7 @@ namespace Wabbajack.BuildServer.Test
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task<Uri> MakeModList()
|
|
||||||
{
|
|
||||||
var archive_data = Encoding.UTF8.GetBytes("Cheese for Everyone!");
|
|
||||||
var test_archive_path = "test_archive.txt".RelativeTo(Fixture.ServerPublicFolder);
|
|
||||||
await test_archive_path.WriteAllBytesAsync(archive_data);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ModListData = new ModList();
|
|
||||||
ModListData.Archives.Add(
|
|
||||||
new Archive(new HTTPDownloader.State(MakeURL("test_archive.txt")))
|
|
||||||
{
|
|
||||||
Hash = await test_archive_path.FileHashAsync(),
|
|
||||||
Name = "test_archive",
|
|
||||||
Size = test_archive_path.Size,
|
|
||||||
});
|
|
||||||
|
|
||||||
var modListPath = "test_modlist.wabbajack".RelativeTo(Fixture.ServerPublicFolder);
|
|
||||||
|
|
||||||
await using (var fs = modListPath.Create())
|
|
||||||
{
|
|
||||||
using var za = new ZipArchive(fs, ZipArchiveMode.Create);
|
|
||||||
var entry = za.CreateEntry("modlist");
|
|
||||||
await using var es = entry.Open();
|
|
||||||
ModListData.ToJson(es);
|
|
||||||
}
|
|
||||||
|
|
||||||
ModListMetaData = new List<ModlistMetadata>
|
|
||||||
{
|
|
||||||
new ModlistMetadata
|
|
||||||
{
|
|
||||||
Official = false,
|
|
||||||
Author = "Test Suite",
|
|
||||||
Description = "A test",
|
|
||||||
DownloadMetadata = new DownloadMetadata
|
|
||||||
{
|
|
||||||
Hash = await modListPath.FileHashAsync(),
|
|
||||||
Size = modListPath.Size
|
|
||||||
},
|
|
||||||
Links = new ModlistMetadata.LinksObject
|
|
||||||
{
|
|
||||||
MachineURL = "test_list",
|
|
||||||
Download = MakeURL("test_modlist.wabbajack")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new ModlistMetadata
|
|
||||||
{
|
|
||||||
Official = true,
|
|
||||||
Author = "Test Suite",
|
|
||||||
Description = "A list with a broken hash",
|
|
||||||
DownloadMetadata = new DownloadMetadata()
|
|
||||||
{
|
|
||||||
Hash = Hash.FromLong(42),
|
|
||||||
Size = 42
|
|
||||||
},
|
|
||||||
Links = new ModlistMetadata.LinksObject
|
|
||||||
{
|
|
||||||
MachineURL = "broken_list",
|
|
||||||
Download = MakeURL("test_modlist.wabbajack")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var metadataPath = "test_mod_list_metadata.json".RelativeTo(Fixture.ServerPublicFolder);
|
|
||||||
|
|
||||||
ModListMetaData.ToJson(metadataPath);
|
|
||||||
|
|
||||||
return new Uri(MakeURL("test_mod_list_metadata.json"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModList ModListData { get; set; }
|
|
||||||
|
|
||||||
public List<ModlistMetadata> ModListMetaData { get; set; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
134
Wabbajack.Server.Test/ModlistUpdater.cs
Normal file
134
Wabbajack.Server.Test/ModlistUpdater.cs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dapper;
|
||||||
|
using Wabbajack.BuildServer;
|
||||||
|
using Wabbajack.BuildServer.Test;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
using Wabbajack.Lib;
|
||||||
|
using Wabbajack.Lib.Downloaders;
|
||||||
|
using Wabbajack.Lib.ModListRegistry;
|
||||||
|
using Wabbajack.Lib.NexusApi;
|
||||||
|
using Wabbajack.Server.DataLayer;
|
||||||
|
using Wabbajack.Server.DTOs;
|
||||||
|
using Wabbajack.Server.Services;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Wabbajack.Server.Test
|
||||||
|
{
|
||||||
|
public class ModlistUpdater : ABuildServerSystemTest
|
||||||
|
{
|
||||||
|
public ModlistUpdater(ITestOutputHelper output, SingletonAdaptor<BuildServerFixture> fixture) : base(output,
|
||||||
|
fixture)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanIndexAndUpdateFiles()
|
||||||
|
{
|
||||||
|
var validator = Fixture.GetService<ListValidator>();
|
||||||
|
var nonNexus = Fixture.GetService<NonNexusDownloadValidator>();
|
||||||
|
var modLists = await MakeModList();
|
||||||
|
Consts.ModlistMetadataURL = modLists.ToString();
|
||||||
|
|
||||||
|
|
||||||
|
var listDownloader = Fixture.GetService<ModListDownloader>();
|
||||||
|
var downloader = Fixture.GetService<ArchiveDownloader>();
|
||||||
|
var archiver = Fixture.GetService<ArchiveMaintainer>();
|
||||||
|
|
||||||
|
var sql = Fixture.GetService<SqlService>();
|
||||||
|
var modId = long.MaxValue >> 1;
|
||||||
|
var oldFileId = long.MaxValue >> 2;
|
||||||
|
var newFileId = (long.MaxValue >> 2) + 1;
|
||||||
|
|
||||||
|
var oldFileData = Encoding.UTF8.GetBytes("Cheese for Everyone!");
|
||||||
|
var newFileData = Encoding.UTF8.GetBytes("Forks for Everyone!");
|
||||||
|
var oldDataHash = oldFileData.xxHash();
|
||||||
|
var newDataHash = newFileData.xxHash();
|
||||||
|
|
||||||
|
Assert.Equal(2, await listDownloader.CheckForNewLists());
|
||||||
|
Assert.Equal(1, await downloader.Execute());
|
||||||
|
Assert.Equal(0, await nonNexus.Execute());
|
||||||
|
Assert.Equal(0, await validator.Execute());
|
||||||
|
|
||||||
|
|
||||||
|
Assert.True(archiver.HaveArchive(oldDataHash));
|
||||||
|
Assert.False(archiver.HaveArchive(newDataHash));
|
||||||
|
|
||||||
|
var status = (await ModlistMetadata.LoadFromGithub()).FirstOrDefault(l => l.Links.MachineURL == "test_list");
|
||||||
|
Assert.Equal(0, status.ValidationSummary.Failed);
|
||||||
|
|
||||||
|
|
||||||
|
// Update the archive
|
||||||
|
await "test_archive.txt".RelativeTo(Fixture.ServerPublicFolder).WriteAllBytesAsync(newFileData);
|
||||||
|
|
||||||
|
// Nothing new to do
|
||||||
|
Assert.Equal(0, await listDownloader.CheckForNewLists());
|
||||||
|
Assert.Equal(0, await downloader.Execute());
|
||||||
|
|
||||||
|
// List now fails after we check the manual link
|
||||||
|
Assert.Equal(1, await nonNexus.Execute());
|
||||||
|
Assert.Equal(1, await validator.Execute());
|
||||||
|
|
||||||
|
/*
|
||||||
|
Assert.True(await sql.HaveIndexdFile(oldDataHash));
|
||||||
|
Assert.True(await sql.HaveIndexdFile(newDataHash));
|
||||||
|
|
||||||
|
var settings = Fixture.GetService<AppSettings>();
|
||||||
|
Assert.Equal($"Oldfile_{oldDataHash.ToHex()}_".RelativeTo(Fixture.ServerArchivesFolder), settings.PathForArchive(oldDataHash));
|
||||||
|
Assert.Equal($"Newfile_{newDataHash.ToHex()}_".RelativeTo(Fixture.ServerArchivesFolder), settings.PathForArchive(newDataHash));
|
||||||
|
|
||||||
|
Utils.Log($"Download Updating {oldDataHash} -> {newDataHash}");
|
||||||
|
await using var conn = await sql.Open();
|
||||||
|
|
||||||
|
await conn.ExecuteAsync("DELETE FROM dbo.DownloadStates WHERE Hash in (@OldHash, @NewHash);",
|
||||||
|
new {OldHash = (long)oldDataHash, NewHash = (long)newDataHash});
|
||||||
|
|
||||||
|
await sql.AddDownloadState(oldDataHash, new NexusDownloader.State
|
||||||
|
{
|
||||||
|
Game = Game.Oblivion,
|
||||||
|
ModID = modId,
|
||||||
|
FileID = oldFileId
|
||||||
|
});
|
||||||
|
|
||||||
|
await sql.AddDownloadState(newDataHash, new NexusDownloader.State
|
||||||
|
{
|
||||||
|
Game = Game.Oblivion,
|
||||||
|
ModID = modId,
|
||||||
|
FileID = newFileId
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.NotNull(await sql.GetNexusStateByHash(oldDataHash));
|
||||||
|
Assert.NotNull(await sql.GetNexusStateByHash(newDataHash));
|
||||||
|
|
||||||
|
// No nexus info, so no upgrade
|
||||||
|
var noUpgrade = await ClientAPI.GetModUpgrade(oldDataHash);
|
||||||
|
Assert.Null(noUpgrade);
|
||||||
|
|
||||||
|
// Add Nexus info
|
||||||
|
await sql.AddNexusModFiles(Game.Oblivion, modId, DateTime.Now,
|
||||||
|
new NexusApiClient.GetModFilesResponse
|
||||||
|
{
|
||||||
|
files = new List<NexusFileInfo>
|
||||||
|
{
|
||||||
|
new NexusFileInfo {category_name = "MAIN", file_id = newFileId, file_name = "New File"},
|
||||||
|
new NexusFileInfo {category_name = null, file_id = oldFileId, file_name = "Old File"}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var enqueuedUpgrade = await ClientAPI.GetModUpgrade(oldDataHash);
|
||||||
|
|
||||||
|
// Not Null because upgrade was enqueued
|
||||||
|
Assert.NotNull(enqueuedUpgrade);
|
||||||
|
|
||||||
|
await RunAllJobs();
|
||||||
|
|
||||||
|
Assert.True($"{oldDataHash.ToHex()}_{newDataHash.ToHex()}".RelativeTo(Fixture.ServerUpdatesFolder).IsFile);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
Wabbajack.Server/DTOs/Patch.cs
Normal file
14
Wabbajack.Server/DTOs/Patch.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
|
||||||
|
namespace Wabbajack.Server.DTOs
|
||||||
|
{
|
||||||
|
public class Patch
|
||||||
|
{
|
||||||
|
public ArchiveDownload Src { get; set; }
|
||||||
|
public ArchiveDownload Dest { get; set; }
|
||||||
|
public Hash PatchHash { get; set; }
|
||||||
|
public long PatchSize { get; set; }
|
||||||
|
public DateTime? Finished { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -37,6 +37,26 @@ namespace Wabbajack.Server.DataLayer
|
|||||||
return (await conn.QueryAsync<(Hash, string)>("SELECT Hash, PrimaryKeyString FROM ArchiveDownloads")).ToHashSet();
|
return (await conn.QueryAsync<(Hash, string)>("SELECT Hash, PrimaryKeyString FROM ArchiveDownloads")).ToHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<ArchiveDownload> GetArchiveDownload(Guid id)
|
||||||
|
{
|
||||||
|
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 Id = @id",
|
||||||
|
new {Id = id});
|
||||||
|
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> GetNextPendingDownload(bool ignoreNexus = false)
|
public async Task<ArchiveDownload> GetNextPendingDownload(bool ignoreNexus = false)
|
||||||
{
|
{
|
||||||
await using var conn = await Open();
|
await using var conn = await Open();
|
||||||
|
65
Wabbajack.Server/DataLayer/Patches.cs
Normal file
65
Wabbajack.Server/DataLayer/Patches.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dapper;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
using Wabbajack.Server.DTOs;
|
||||||
|
|
||||||
|
namespace Wabbajack.Server.DataLayer
|
||||||
|
{
|
||||||
|
public partial class SqlService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a patch record
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="patch"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task AddPatch(Patch patch)
|
||||||
|
{
|
||||||
|
await using var conn = await Open();
|
||||||
|
await conn.ExecuteAsync("INSERT INTO dbo.Patches (SrcId, DestId) VALUES (@SrcId, @DestId)",
|
||||||
|
new {SrcId = patch.Src.Id, DestId = patch.Dest.Id});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a patch record
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="patch"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task FinializePatch(Patch patch)
|
||||||
|
{
|
||||||
|
await using var conn = await Open();
|
||||||
|
await conn.ExecuteAsync("UPDATE dbo.Patches SET PatchSize = @Size, PatchHash = @PatchHash, Finished = @Finished WHERE SrcId = @SrcId AND DestID = @DestId",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
SrcId = patch.Src.Id,
|
||||||
|
DestId = patch.Dest.Id,
|
||||||
|
PatchHash = patch.PatchHash,
|
||||||
|
PatchSize = patch.PatchSize,
|
||||||
|
Finshed = patch.Finished
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Patch> FindPatch(Guid src, Guid dest)
|
||||||
|
{
|
||||||
|
await using var conn = await Open();
|
||||||
|
var patch = await conn.QueryFirstOrDefaultAsync<(Hash, long, DateTime?)>(
|
||||||
|
"SELECT PatchHash, PatchSize, Finished FROM dbo.Patches WHERE SrcId = @SrcId AND DestId = @DestId",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
SrcId = src,
|
||||||
|
DestId = dest
|
||||||
|
});
|
||||||
|
if (patch == default)
|
||||||
|
return default(Patch);
|
||||||
|
|
||||||
|
return new Patch {
|
||||||
|
Src = await GetArchiveDownload(src),
|
||||||
|
Dest = await GetArchiveDownload(dest),
|
||||||
|
PatchHash = patch.Item1,
|
||||||
|
PatchSize = patch.Item2,
|
||||||
|
Finished = patch.Item3
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -67,6 +67,7 @@ namespace Wabbajack.Server.Services
|
|||||||
|
|
||||||
if (nextDownload.Archive.Hash != default && hash != nextDownload.Archive.Hash)
|
if (nextDownload.Archive.Hash != default && hash != nextDownload.Archive.Hash)
|
||||||
{
|
{
|
||||||
|
_logger.Log(LogLevel.Warning, $"Downloaded archive hashes don't match for {nextDownload.Archive.State.PrimaryKeyString} {nextDownload.Archive.Hash} {nextDownload.Archive.Size} vs {hash} {tempPath.Path.Size}");
|
||||||
await nextDownload.Fail(_sql, "Invalid Hash");
|
await nextDownload.Fail(_sql, "Invalid Hash");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user