wabbajack/Wabbajack.Downloaders.ModDB/ModDBDownloader.cs

170 lines
5.8 KiB
C#
Raw Normal View History

2021-09-27 12:42:46 +00:00
using System;
using System.Collections.Generic;
using System.IO;
2021-09-27 12:42:46 +00:00
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using HtmlAgilityPack;
using Microsoft.Extensions.Logging;
using Wabbajack.Downloaders.Interfaces;
using Wabbajack.DTOs;
using Wabbajack.DTOs.DownloadStates;
using Wabbajack.DTOs.Validation;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Networking.Http;
2021-09-27 12:42:46 +00:00
using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.Paths;
using Wabbajack.RateLimiter;
2021-10-23 16:51:17 +00:00
namespace Wabbajack.Downloaders.ModDB;
public class ModDBDownloader : ADownloader<DTOs.DownloadStates.ModDB>, IUrlDownloader, IProxyable
2021-09-27 12:42:46 +00:00
{
2021-10-23 16:51:17 +00:00
private readonly IHttpDownloader _downloader;
private readonly HttpClient _httpClient;
private readonly ILogger<ModDBDownloader> _logger;
public ModDBDownloader(ILogger<ModDBDownloader> logger, HttpClient httpClient, IHttpDownloader downloader)
2021-09-27 12:42:46 +00:00
{
2021-10-23 16:51:17 +00:00
_logger = logger;
_httpClient = httpClient;
_downloader = downloader;
}
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
public override Task<bool> Prepare()
{
return Task.FromResult(true);
}
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
public override bool IsAllowed(ServerAllowList allowList, IDownloadState state)
{
return true;
}
public override IDownloadState? Resolve(IReadOnlyDictionary<string, string> iniData)
{
if (iniData.ContainsKey("directURL") &&
iniData["directURL"].StartsWith("https://www.moddb.com/downloads/start") &&
Uri.TryCreate(iniData["directURL"], UriKind.Absolute, out var uri))
2021-09-27 12:42:46 +00:00
{
2021-10-23 16:51:17 +00:00
var state = new DTOs.DownloadStates.ModDB
2021-09-27 12:42:46 +00:00
{
2021-10-23 16:51:17 +00:00
Url = uri
};
return state;
2021-09-27 12:42:46 +00:00
}
2021-10-23 16:51:17 +00:00
return null;
}
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
public override Priority Priority => Priority.Normal;
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
public IDownloadState? Parse(Uri uri)
{
if (!uri.ToString().StartsWith("https://www.moddb.com/downloads/start"))
return null;
return new DTOs.DownloadStates.ModDB {Url = uri};
}
public Uri UnParse(IDownloadState state)
{
return ((DTOs.DownloadStates.ModDB) state).Url;
}
public async Task<T> DownloadStream<T>(Archive archive, Func<Stream, Task<T>> fn, CancellationToken token)
{
var state = archive.State as DTOs.DownloadStates.ModDB;
2022-06-09 21:05:26 +00:00
foreach (var url in await GetDownloadUrls(state!))
{
2022-06-09 21:05:26 +00:00
try
{
2022-06-09 21:05:26 +00:00
var msg = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(url)
};
using var response = await _httpClient.SendAsync(msg, HttpCompletionOption.ResponseHeadersRead, token);
if (!response.IsSuccessStatusCode)
continue;
HttpException.ThrowOnFailure(response);
await using var stream = await response.Content.ReadAsStreamAsync(token);
return await fn(stream);
}
catch (Exception ex)
{
_logger.LogError(ex, "While downloading from ModDB");
throw;
}
}
2022-06-09 21:05:26 +00:00
_logger.LogError("All servers were invalid downloading from ModDB {Uri}", state.Url);
return default;
}
2021-10-23 16:51:17 +00:00
public override async Task<Hash> Download(Archive archive, DTOs.DownloadStates.ModDB state,
AbsolutePath destination, IJob job, CancellationToken token)
{
var urls = await GetDownloadUrls(state);
foreach (var (url, idx) in urls.Zip(Enumerable.Range(0, urls.Length), (s, i) => (s, i)))
try
2021-09-27 12:42:46 +00:00
{
2021-10-23 16:51:17 +00:00
var msg = new HttpRequestMessage
2021-09-27 12:42:46 +00:00
{
2021-10-23 16:51:17 +00:00
Method = HttpMethod.Get,
RequestUri = new Uri(url)
2021-09-27 12:42:46 +00:00
};
2021-10-23 16:51:17 +00:00
return await _downloader.Download(msg, destination, job, token);
}
catch (Exception)
{
if (idx == urls.Length - 1)
throw;
_logger.LogInformation("Download from {url} failed, trying next mirror", url);
2021-09-27 12:42:46 +00:00
}
2021-10-23 16:51:17 +00:00
return default;
}
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
private async Task<string[]> GetDownloadUrls(DTOs.DownloadStates.ModDB state, CancellationToken? token = null)
{
var modId = state.Url.AbsolutePath.Split('/').Reverse().FirstOrDefault(f => int.TryParse(f, out _));
if (modId == default)
return Array.Empty<string>();
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
var data = await _httpClient.GetStringAsync($"https://www.moddb.com/downloads/start/{modId}/all",
token ?? CancellationToken.None);
var doc = new HtmlDocument();
doc.LoadHtml(data);
var mirrors = doc.DocumentNode.Descendants().Where(d => d.NodeType == HtmlNodeType.Element && d.HasClass("row"))
.Select(d => new
{
Link = "https://www.moddb.com" +
d.Descendants().Where(s => s.Id == "downloadon")
.Select(i => i.GetAttributeValue("href", ""))
.FirstOrDefault(),
Load = d.Descendants().Where(s => s.HasClass("subheading"))
.Select(i => i.InnerHtml.Split(',')
.Last()
.Split('%')
.Select(v => double.TryParse(v, out var dr) ? dr : double.MaxValue)
.First())
.FirstOrDefault()
})
.OrderBy(d => d.Load)
.ToList();
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
return mirrors.Select(d => d.Link).ToArray();
}
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
public override async Task<bool> Verify(Archive archive, DTOs.DownloadStates.ModDB archiveState, IJob job,
CancellationToken token)
{
var urls = await GetDownloadUrls(archiveState, token);
return urls.Any();
2021-09-27 12:42:46 +00:00
}
2021-10-23 16:51:17 +00:00
public override IEnumerable<string> MetaIni(Archive a, DTOs.DownloadStates.ModDB state)
{
return new[] {$"directURL={state.Url}"};
}
}