Fixes for author APIs

This commit is contained in:
Timothy Baldridge
2020-03-30 14:38:46 -06:00
parent 8b9210eeb0
commit 160ac8a4c3
10 changed files with 96 additions and 37 deletions

View File

@ -31,10 +31,13 @@ namespace Wabbajack.BuildServer.Test
$"WabbajackSettings:ArchiveDir={"archives".RelativeTo(AbsolutePath.EntryPoint)}", $"WabbajackSettings:ArchiveDir={"archives".RelativeTo(AbsolutePath.EntryPoint)}",
$"WabbajackSettings:TempFolder={ServerTempFolder}", $"WabbajackSettings:TempFolder={ServerTempFolder}",
$"WabbajackSettings:SQLConnection={PublicConnStr}", $"WabbajackSettings:SQLConnection={PublicConnStr}",
$"WabbajackSettings:BunnyCDN_User=TEST",
$"WabbajackSettings:BunnyCDN_Password=TEST",
}, true); }, true);
_host = builder.Build(); _host = builder.Build();
_token = new CancellationTokenSource(); _token = new CancellationTokenSource();
_task = _host.RunAsync(_token.Token); _task = _host.RunAsync(_token.Token);
Consts.WabbajackBuildServerUri = new Uri("http://localhost:8080");
} }
public void Dispose() public void Dispose()
@ -52,16 +55,19 @@ namespace Wabbajack.BuildServer.Test
private readonly IDisposable _unsubMsgs; private readonly IDisposable _unsubMsgs;
private readonly IDisposable _unsubErr; private readonly IDisposable _unsubErr;
protected Client _authedClient; protected Client _authedClient;
protected WorkQueue _queue;
public ABuildServerSystemTest(ITestOutputHelper output, BuildServerFixture fixture) : base(output) public ABuildServerSystemTest(ITestOutputHelper output, BuildServerFixture fixture) : base(output)
{ {
Filters.Clear();
_unsubMsgs = Utils.LogMessages.OfType<IInfo>().Subscribe(onNext: msg => XunitContext.WriteLine(msg.ShortDescription)); _unsubMsgs = Utils.LogMessages.OfType<IInfo>().Subscribe(onNext: msg => XunitContext.WriteLine(msg.ShortDescription));
_unsubErr = Utils.LogMessages.OfType<IUserIntervention>().Subscribe(msg => _unsubErr = Utils.LogMessages.OfType<IUserIntervention>().Subscribe(msg =>
XunitContext.WriteLine("ERROR: User intervention required: " + msg.ShortDescription)); XunitContext.WriteLine("ERROR: User intervention required: " + msg.ShortDescription));
_client = new Client(); _client = new Client();
_authedClient = new Client(); _authedClient = new Client();
_authedClient.Headers.Add(("x-api-key", fixture.APIKey)); _authedClient.Headers.Add(("x-api-key", fixture.APIKey));
_queue = new WorkQueue();
Fixture = fixture; Fixture = fixture;
} }

View File

@ -4,6 +4,8 @@ using System.Threading.Tasks;
using Wabbajack.BuildServer.Model.Models; using Wabbajack.BuildServer.Model.Models;
using Wabbajack.BuildServer.Models; using Wabbajack.BuildServer.Models;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.FileUploader;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
using Xunit.Priority; using Xunit.Priority;
@ -33,7 +35,7 @@ namespace Wabbajack.BuildServer.Test
} }
[Fact, Priority(1)] [Fact, Priority(1)]
public async Task CanLoadUploadedFiles() public async Task CanListMyUploadedFiles()
{ {
var result = (await _authedClient.GetStringAsync(MakeURL("uploaded_files/list"))).FromJSONString<string[]>(); var result = (await _authedClient.GetStringAsync(MakeURL("uploaded_files/list"))).FromJSONString<string[]>();
Utils.Log("Loaded: " + result); Utils.Log("Loaded: " + result);
@ -47,5 +49,26 @@ namespace Wabbajack.BuildServer.Test
Assert.DoesNotContain("file3-17b3e918-8409-48e6-b7ff-6af858bfd1ba.zip", result); Assert.DoesNotContain("file3-17b3e918-8409-48e6-b7ff-6af858bfd1ba.zip", result);
} }
[Fact]
public async Task CanUploadFilesUsingClientApi()
{
using (var file = new TempFile())
{
var data = new byte[1024 * 1024 * 8 * 4];
await using (var fs = file.Path.Create())
{
await fs.WriteAsync(data);
}
Utils.Log($"Uploading {file.Path.Size.ToFileSizeString()} file");
var result = await AuthorAPI.UploadFile(file.Path,
progress => Utils.Log($"Uploading : {progress * 100}%"), Fixture.APIKey);
Utils.Log($"Result {result}");
Assert.StartsWith("https://wabbajackpush.b-cdn.net/" +(string)file.Path.FileNameWithoutExtension, result);
}
}
} }
} }

View File

@ -15,6 +15,8 @@ namespace Wabbajack.BuildServer
public string TempFolder { get; set; } public string TempFolder { get; set; }
public AbsolutePath TempPath => (AbsolutePath)TempFolder;
public bool JobScheduler { get; set; } public bool JobScheduler { get; set; }
public bool JobRunner { get; set; } public bool JobRunner { get; set; }

View File

@ -50,7 +50,7 @@ namespace Wabbajack.BuildServer.Controllers
_writeLocks.GetOrAdd(key, new AsyncLock()); _writeLocks.GetOrAdd(key, new AsyncLock());
System.IO.File.Create(Path.Combine("public", "tmp_files", key)).Close(); await using var fs = _settings.TempPath.Combine(key).Create();
Utils.Log($"Starting Ingest for {key}"); Utils.Log($"Starting Ingest for {key}");
return Ok(key); return Ok(key);
} }
@ -62,20 +62,24 @@ namespace Wabbajack.BuildServer.Controllers
{ {
if (!Key.All(a => HexChars.Contains(a))) if (!Key.All(a => HexChars.Contains(a)))
return BadRequest("NOT A VALID FILENAME"); return BadRequest("NOT A VALID FILENAME");
Utils.Log($"Writing at position {Offset} in ingest file {Key}");
var ms = new MemoryStream(); var ms = new MemoryStream();
await Request.Body.CopyToAsync(ms); await Request.Body.CopyToAsync(ms);
ms.Position = 0; ms.Position = 0;
Utils.Log($"Writing {ms.Length} at position {Offset} in ingest file {Key}");
long position; long position;
using (var _ = await _writeLocks[Key].Wait()) using (var _ = await _writeLocks[Key].Wait())
await using (var file = System.IO.File.Open(Path.Combine("public", "tmp_files", Key), FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{ {
await using var file = _settings.TempPath.Combine(Key).WriteShared();
file.Position = Offset; file.Position = Offset;
await ms.CopyToAsync(file); await ms.CopyToAsync(file);
position = file.Position; position = Offset + ms.Length;
} }
Utils.Log($"Wrote {ms.Length} as position {Offset} result {position}");
return Ok(position); return Ok(position);
} }
@ -132,7 +136,8 @@ namespace Wabbajack.BuildServer.Controllers
var originalName = $"{parts[0]}{parts[2]}"; var originalName = $"{parts[0]}{parts[2]}";
var finalPath = "public".RelativeTo(AbsolutePath.EntryPoint).Combine("files", finalName); var finalPath = "public".RelativeTo(AbsolutePath.EntryPoint).Combine("files", finalName);
"public".RelativeTo(AbsolutePath.EntryPoint).MoveTo(finalPath); _settings.TempPath.Combine(Key).MoveTo(finalPath);
var hash = await finalPath.FileHashAsync(); var hash = await finalPath.FileHashAsync();
if (expectedHash != hash) if (expectedHash != hash)
@ -151,8 +156,8 @@ namespace Wabbajack.BuildServer.Controllers
Size = finalPath.Size, Size = finalPath.Size,
CDNName = "wabbajackpush" CDNName = "wabbajackpush"
}; };
await Db.UploadedFiles.InsertOneAsync(record); await SQL.AddUploadedFile(record);
await Db.Jobs.InsertOneAsync(new Job await SQL.EnqueueJob(new Job
{ {
Priority = Job.JobPriority.High, Payload = new UploadToCDN {FileId = record.Id} Priority = Job.JobPriority.High, Payload = new UploadToCDN {FileId = record.Id}
}); });

View File

@ -28,6 +28,11 @@ namespace Wabbajack.BuildServer.Models.Jobs
TOP: TOP:
var file = await db.UploadedFiles.AsQueryable().Where(f => f.Id == FileId).FirstOrDefaultAsync(); var file = await db.UploadedFiles.AsQueryable().Where(f => f.Id == FileId).FirstOrDefaultAsync();
if (settings.BunnyCDN_User == "TEST" && settings.BunnyCDN_Password == "TEST")
{
return JobResult.Success();
}
using (var client = new FtpClient("storage.bunnycdn.com")) using (var client = new FtpClient("storage.bunnycdn.com"))
{ {
client.Credentials = new NetworkCredential(settings.BunnyCDN_User, settings.BunnyCDN_Password); client.Credentials = new NetworkCredential(settings.BunnyCDN_User, settings.BunnyCDN_Password);

View File

@ -107,6 +107,7 @@ namespace Wabbajack.Common
public static string WabbajackCacheLocation = "http://build.wabbajack.org/nexus_api_cache/"; public static string WabbajackCacheLocation = "http://build.wabbajack.org/nexus_api_cache/";
public static string WabbajackCacheHostname = "build.wabbajack.org"; public static string WabbajackCacheHostname = "build.wabbajack.org";
public static Uri WabbajackBuildServerUri = new Uri("https://build.wabbajack.org");
public static int WabbajackCachePort = 80; public static int WabbajackCachePort = 80;
public static int MaxHTTPRetries = 4; public static int MaxHTTPRetries = 4;
public static RelativePath MO2ModFolderName = (RelativePath)"mods"; public static RelativePath MO2ModFolderName = (RelativePath)"mods";

View File

@ -384,6 +384,11 @@ namespace Wabbajack.Common
{ {
return File.Open(_path, FileMode.Open, FileAccess.Read); return File.Open(_path, FileMode.Open, FileAccess.Read);
} }
public FileStream WriteShared()
{
return File.Open(_path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
}
} }
public struct RelativePath : IPath, IEquatable<RelativePath>, IComparable<RelativePath> public struct RelativePath : IPath, IEquatable<RelativePath>, IComparable<RelativePath>

View File

@ -1138,6 +1138,14 @@ namespace Wabbajack.Common
return bytes.ToHex(); return bytes.ToHex();
} }
public static byte[] RandomData(int size)
{
var random = new Random();
byte[] bytes = new byte[size];
random.NextBytes(bytes);
return bytes;
}
public static async Task CopyFileAsync(string src, string dest) public static async Task CopyFileAsync(string src, string dest)
{ {
await using var s = File.OpenRead(src); await using var s = File.OpenRead(src);

View File

@ -21,24 +21,25 @@ namespace Wabbajack.Lib.FileUploader
{ {
public static IObservable<bool> HaveAuthorAPIKey => Utils.HaveEncryptedJsonObservable("author-api-key.txt"); public static IObservable<bool> HaveAuthorAPIKey => Utils.HaveEncryptedJsonObservable("author-api-key.txt");
public static async Task<string> GetAPIKey() public static async Task<string> GetAPIKey(string apiKey = null)
{ {
return (await Consts.LocalAppDataPath.Combine("author-api-key.txt").ReadAllTextAsync()).Trim(); return apiKey ?? (await Consts.LocalAppDataPath.Combine("author-api-key.txt").ReadAllTextAsync()).Trim();
} }
public static readonly Uri UploadURL = new Uri("https://build.wabbajack.org/upload_file"); public static Uri UploadURL => new Uri($"{Consts.WabbajackBuildServerUri}upload_file");
public static long BLOCK_SIZE = (long)1024 * 1024 * 2; public static long BLOCK_SIZE = (long)1024 * 1024 * 2;
public static int MAX_CONNECTIONS = 8; public static int MAX_CONNECTIONS = 8;
public static Task<string> UploadFile(WorkQueue queue, AbsolutePath filename, Action<double> progressFn) public static Task<string> UploadFile(AbsolutePath filename, Action<double> progressFn, string apikey=null)
{ {
var tcs = new TaskCompletionSource<string>(); var tcs = new TaskCompletionSource<string>();
Task.Run(async () => Task.Run(async () =>
{ {
var client = await GetAuthorizedClient(); var client = await GetAuthorizedClient(apikey);
var fsize = filename.Size; var fsize = filename.Size;
var hash_task = filename.FileHashAsync(); var hashTask = filename.FileHashAsync();
Utils.Log($"{UploadURL}/{filename.FileName.ToString()}/start");
var response = await client.PutAsync($"{UploadURL}/{filename.FileName.ToString()}/start", new StringContent("")); var response = await client.PutAsync($"{UploadURL}/{filename.FileName.ToString()}/start", new StringContent(""));
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
@ -58,36 +59,39 @@ namespace Wabbajack.Lib.FileUploader
{ {
iqueue.Report("Starting Upload", Percent.One); iqueue.Report("Starting Upload", Percent.One);
await Blocks(fsize) await Blocks(fsize)
.PMap(iqueue, async block_idx => .PMap(iqueue, async blockIdx =>
{ {
if (tcs.Task.IsFaulted) return; if (tcs.Task.IsFaulted) return;
var block_offset = block_idx * BLOCK_SIZE; var blockOffset = blockIdx * BLOCK_SIZE;
var block_size = block_offset + BLOCK_SIZE > fsize var blockSize = blockOffset + BLOCK_SIZE > fsize
? fsize - block_offset ? fsize - blockOffset
: BLOCK_SIZE; : BLOCK_SIZE;
Interlocked.Add(ref sent, block_size); Interlocked.Add(ref sent, blockSize);
progressFn((double)sent / fsize); progressFn((double)sent / fsize);
await using var fs = filename.OpenRead(); var data = new byte[blockSize];
fs.Position = block_offset; await using (var fs = filename.OpenRead())
var data = new byte[block_size]; {
fs.Position = blockOffset;
await fs.ReadAsync(data, 0, data.Length); await fs.ReadAsync(data, 0, data.Length);
}
response = await client.PutAsync(UploadURL + $"/{key}/data/{block_offset}", var offsetResponse = await client.PutAsync(UploadURL + $"/{key}/data/{blockOffset}",
new ByteArrayContent(data)); new ByteArrayContent(data));
if (!response.IsSuccessStatusCode) if (!offsetResponse.IsSuccessStatusCode)
{ {
tcs.SetException(new Exception($"Put Error: {response.StatusCode} {response.ReasonPhrase}")); Utils.Log(await offsetResponse.Content.ReadAsStringAsync());
tcs.SetException(new Exception($"Put Error: {offsetResponse.StatusCode} {offsetResponse.ReasonPhrase}"));
return; return;
} }
var val = long.Parse(await response.Content.ReadAsStringAsync()); var val = long.Parse(await offsetResponse.Content.ReadAsStringAsync());
if (val != block_offset + data.Length) if (val != blockOffset + data.Length)
{ {
tcs.SetResult($"Sync Error {val} vs {block_offset + data.Length}"); tcs.SetResult($"Sync Error {val} vs {blockOffset + data.Length} Offset {blockOffset} Size {data.Length}");
tcs.SetException(new Exception($"Sync Error {val} vs {block_offset + data.Length}")); tcs.SetException(new Exception($"Sync Error {val} vs {blockOffset + data.Length}"));
} }
}); });
} }
@ -95,7 +99,7 @@ namespace Wabbajack.Lib.FileUploader
if (!tcs.Task.IsFaulted) if (!tcs.Task.IsFaulted)
{ {
progressFn(1.0); progressFn(1.0);
var hash = (await hash_task).ToHex(); var hash = (await hashTask).ToHex();
response = await client.PutAsync(UploadURL + $"/{key}/finish/{hash}", new StringContent("")); response = await client.PutAsync(UploadURL + $"/{key}/finish/{hash}", new StringContent(""));
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
tcs.SetResult(await response.Content.ReadAsStringAsync()); tcs.SetResult(await response.Content.ReadAsStringAsync());
@ -109,10 +113,10 @@ namespace Wabbajack.Lib.FileUploader
return tcs.Task; return tcs.Task;
} }
public static async Task<Common.Http.Client> GetAuthorizedClient() public static async Task<Common.Http.Client> GetAuthorizedClient(string apiKey=null)
{ {
var client = new Common.Http.Client(); var client = new Common.Http.Client();
client.Headers.Add(("X-API-KEY", await GetAPIKey())); client.Headers.Add(("X-API-KEY", await GetAPIKey(apiKey)));
return client; return client;
} }

View File

@ -59,7 +59,7 @@ namespace Wabbajack
_isUploading.OnNext(true); _isUploading.OnNext(true);
try try
{ {
FinalUrl = await AuthorAPI.UploadFile(Queue, Picker.TargetPath, FinalUrl = await AuthorAPI.UploadFile(Picker.TargetPath,
progress => UploadProgress = progress); progress => UploadProgress = progress);
} }
catch (Exception ex) catch (Exception ex)