Files
nadekobot/src/NadekoBot/Services/Common/ImageLoader.cs

142 lines
4.8 KiB
C#

using Newtonsoft.Json.Linq;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Serilog;
namespace NadekoBot.Services.Common
{
public class ImageLoader
{
private readonly HttpClient _http;
private readonly ConnectionMultiplexer _con;
public Func<string, RedisKey> GetKey { get; }
private IDatabase _db => _con.GetDatabase();
private readonly List<Task<KeyValuePair<RedisKey, RedisValue>>> uriTasks = new List<Task<KeyValuePair<RedisKey, RedisValue>>>();
public ImageLoader(HttpClient http, ConnectionMultiplexer con, Func<string, RedisKey> getKey)
{
_http = http;
_con = con;
GetKey = getKey;
}
private async Task<byte[]> GetImageData(Uri uri)
{
if (uri.IsFile)
{
try
{
var bytes = await File.ReadAllBytesAsync(uri.LocalPath);
return bytes;
}
catch (Exception ex)
{
Log.Warning(ex, "Failed reading image bytes");
return null;
}
}
else
{
return await _http.GetByteArrayAsync(uri);
}
}
async Task HandleJArray(JArray arr, string key)
{
var tasks = arr.Where(x => x.Type == JTokenType.String)
.Select(async x =>
{
try
{
return await GetImageData((Uri)x).ConfigureAwait(false);
}
catch
{
Log.Error("Error retreiving image for key {Key}: {Data}", key, x);
return null;
}
});
byte[][] vals = Array.Empty<byte[]>();
vals = await Task.WhenAll(tasks).ConfigureAwait(false);
if (vals.Any(x => x is null))
vals = vals.Where(x => x != null).ToArray();
await _db.KeyDeleteAsync(GetKey(key)).ConfigureAwait(false);
await _db.ListRightPushAsync(GetKey(key),
vals.Where(x => x != null)
.Select(x => (RedisValue)x)
.ToArray()).ConfigureAwait(false);
if (arr.Count != vals.Length)
{
Log.Information("{2}/{1} URIs for the key '{0}' have been loaded. Some of the supplied URIs are either unavailable or invalid.", key, arr.Count, vals.Count());
}
}
async Task<KeyValuePair<RedisKey, RedisValue>> HandleUri(Uri uri, string key)
{
try
{
RedisValue data = await GetImageData(uri).ConfigureAwait(false);
return new KeyValuePair<RedisKey, RedisValue>(GetKey(key), data);
}
catch
{
Log.Information("Setting '{0}' image failed. The URI you provided is either unavailable or invalid.", key.ToLowerInvariant());
return new KeyValuePair<RedisKey, RedisValue>("", "");
}
}
Task HandleJObject(JObject obj, string parent = "")
{
string GetParentString()
{
if (string.IsNullOrWhiteSpace(parent))
return "";
else
return parent + "_";
}
List<Task> tasks = new List<Task>();
Task t;
// go through all of the kvps in the object
foreach (var kvp in obj)
{
// if it's a JArray, resole it using jarray method which will
// return task<byte[][]> aka an array of all images' bytes
if (kvp.Value.Type == JTokenType.Array)
{
t = HandleJArray((JArray)kvp.Value, GetParentString() + kvp.Key);
tasks.Add(t);
}
else if (kvp.Value.Type == JTokenType.String)
{
var uriTask = HandleUri((Uri)kvp.Value, GetParentString() + kvp.Key);
uriTasks.Add(uriTask);
}
else if (kvp.Value.Type == JTokenType.Object)
{
t = HandleJObject((JObject)kvp.Value, GetParentString() + kvp.Key);
tasks.Add(t);
}
}
return Task.WhenAll(tasks);
}
public async Task LoadAsync(JObject obj)
{
await HandleJObject(obj).ConfigureAwait(false);
var results = await Task.WhenAll(uriTasks).ConfigureAwait(false);
await _db.StringSetAsync(results.Where(x => x.Key != "").ToArray()).ConfigureAwait(false);
}
}
}