wabbajack/Wabbajack.Lib/AuthorApi/Client.cs

156 lines
6.3 KiB
C#
Raw Normal View History

2020-05-09 22:16:16 +00:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Wabbajack.Common;
2021-07-20 05:08:12 +00:00
using Wabbajack.Lib.GitHub;
2021-07-19 22:36:37 +00:00
using Wabbajack.Lib.ModListRegistry;
2020-05-09 22:16:16 +00:00
namespace Wabbajack.Lib.AuthorApi
{
public class Client
{
public static async Task<Client> Create(string? apiKey = null)
{
var client = await GetAuthorizedClient(apiKey);
return new Client(client);
}
2020-06-26 17:08:30 +00:00
private Client(Wabbajack.Lib.Http.Client client)
2020-05-09 22:16:16 +00:00
{
_client = client;
}
2020-06-26 17:08:30 +00:00
public static async Task<Wabbajack.Lib.Http.Client> GetAuthorizedClient(string? apiKey = null)
2020-05-09 22:16:16 +00:00
{
2020-06-26 17:08:30 +00:00
var client = new Wabbajack.Lib.Http.Client();
2020-05-09 22:16:16 +00:00
client.Headers.Add(("X-API-KEY", await GetAPIKey(apiKey)));
return client;
}
public static string? ApiKeyOverride = null;
2020-06-26 17:08:30 +00:00
private Wabbajack.Lib.Http.Client _client;
2020-05-09 22:16:16 +00:00
public static async ValueTask<string> GetAPIKey(string? apiKey = null)
{
return apiKey ?? (await Consts.LocalAppDataPath.Combine(Consts.AuthorAPIKeyFile).ReadAllTextAsync()).Trim();
}
public static async Task<CDNFileDefinition> GenerateFileDefinition(WorkQueue queue, AbsolutePath path, Action<string, Percent> progressFn)
2020-05-09 22:16:16 +00:00
{
IEnumerable<CDNFilePartDefinition> Blocks(AbsolutePath path)
{
var size = path.Size;
for (long block = 0; block * Consts.UploadedFileBlockSize < size; block ++)
2020-05-09 22:16:16 +00:00
yield return new CDNFilePartDefinition
{
Index = block,
Size = Math.Min(Consts.UploadedFileBlockSize, size - block * Consts.UploadedFileBlockSize),
Offset = block * Consts.UploadedFileBlockSize
2020-05-09 22:16:16 +00:00
};
}
var parts = Blocks(path).ToArray();
var definition = new CDNFileDefinition
{
OriginalFileName = path.FileName,
Size = path.Size,
2021-01-09 19:04:11 +00:00
Hash = await path.FileHashCachedAsync() ?? Hash.Empty,
2020-05-09 22:16:16 +00:00
Parts = await parts.PMap(queue, async part =>
{
progressFn("Hashing file parts", Percent.FactoryPutInRange(part.Index, parts.Length));
var buffer = new byte[part.Size];
await using (var fs = await path.OpenShared())
2020-05-09 22:16:16 +00:00
{
fs.Position = part.Offset;
await fs.ReadAsync(buffer);
}
part.Hash = buffer.xxHash();
return part;
})
};
return definition;
}
2021-07-20 05:08:12 +00:00
public async Task UpdateModListInformation(UpdateRequest metadata)
2021-07-19 22:36:37 +00:00
{
await CircuitBreaker.WithAutoRetryAllAsync(async () =>
{
2021-07-20 05:08:12 +00:00
Utils.Log($"Updating modlist information for {metadata.MachineUrl} - {metadata.Version}");
using var result = await _client.PostAsync($"{Consts.WabbajackBuildServerUri}author_controls/lists/download_metadata",
2021-07-19 22:36:37 +00:00
new StringContent(metadata.ToJson()));
});
}
public async Task<IReadOnlyList<(string MachineURL, Version Version)>> GetMyModlists()
{
var myLists = await _client.GetJsonAsync<string[]>($"{Consts.WabbajackBuildServerUri}author_controls/lists");
List<(string MachineURL, Version Version)> lists = new();
var client = await GitHub.Client.Get();
foreach (var file in Enum.GetValues<GitHub.Client.List>())
{
foreach (var lst in (await client.GetData(file)).Lists)
{
if (myLists.Contains(lst.Links.MachineURL))
{
lists.Add((lst.Links.MachineURL, lst.Version ?? new Version()));
}
}
}
return lists;
}
2020-05-09 22:16:16 +00:00
public async Task<Uri> UploadFile(WorkQueue queue, AbsolutePath path, Action<string, Percent> progressFn)
{
var definition = await GenerateFileDefinition(queue, path, progressFn);
2021-03-19 22:59:38 +00:00
await CircuitBreaker.WithAutoRetryAllAsync(async () =>
2020-05-09 22:16:16 +00:00
{
2021-03-19 22:59:38 +00:00
using var result = await _client.PutAsync($"{Consts.WabbajackBuildServerUri}authored_files/create",
new StringContent(definition.ToJson()));
2020-05-09 22:16:16 +00:00
progressFn("Starting upload", Percent.Zero);
definition.ServerAssignedUniqueId = await result.Content.ReadAsStringAsync();
2021-03-19 22:59:38 +00:00
});
2020-05-09 22:16:16 +00:00
var results = await definition.Parts.PMap(queue, async part =>
{
progressFn("Uploading Part", Percent.FactoryPutInRange(part.Index, definition.Parts.Length));
var buffer = new byte[part.Size];
await using (var fs = await path.OpenShared())
2020-05-09 22:16:16 +00:00
{
fs.Position = part.Offset;
await fs.ReadAsync(buffer);
}
2021-03-19 22:59:38 +00:00
return await CircuitBreaker.WithAutoRetryAllAsync(async () =>
2020-05-09 22:16:16 +00:00
{
2021-03-19 22:59:38 +00:00
using var putResult = await _client.PutAsync(
$"{Consts.WabbajackBuildServerUri}authored_files/{definition.ServerAssignedUniqueId}/part/{part.Index}",
new ByteArrayContent(buffer));
var hash = Hash.FromBase64(await putResult.Content.ReadAsStringAsync());
if (hash != part.Hash)
throw new InvalidDataException("Hashes don't match");
return hash;
});
2020-05-09 22:16:16 +00:00
});
progressFn("Finalizing upload", Percent.Zero);
2021-03-19 22:59:38 +00:00
return await CircuitBreaker.WithAutoRetryAllAsync(async () =>
2020-05-09 22:16:16 +00:00
{
2021-03-19 22:59:38 +00:00
using var result = await _client.PutAsync(
$"{Consts.WabbajackBuildServerUri}authored_files/{definition.ServerAssignedUniqueId}/finish",
new StringContent(definition.ToJson()));
2020-05-09 22:16:16 +00:00
progressFn("Finished", Percent.One);
return new Uri(await result.Content.ReadAsStringAsync());
2021-03-19 22:59:38 +00:00
});
2020-05-09 22:16:16 +00:00
}
}
}