2019-11-19 05:10:07 +00:00
|
|
|
|
using System;
|
2020-01-01 17:11:48 +00:00
|
|
|
|
using System.Collections.Generic;
|
2019-11-19 05:10:07 +00:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net.Http;
|
2020-01-01 16:19:06 +00:00
|
|
|
|
using System.Reflection;
|
2019-11-19 05:10:07 +00:00
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
2020-01-01 16:19:06 +00:00
|
|
|
|
using MongoDB.Driver;
|
2019-11-19 05:10:07 +00:00
|
|
|
|
using Nancy;
|
2020-01-01 17:11:48 +00:00
|
|
|
|
using Newtonsoft.Json;
|
2020-01-01 16:19:06 +00:00
|
|
|
|
using Wabbajack.CacheServer.DTOs;
|
2019-11-19 05:10:07 +00:00
|
|
|
|
using Wabbajack.Common;
|
|
|
|
|
using Wabbajack.Lib.Downloaders;
|
|
|
|
|
using Wabbajack.Lib.NexusApi;
|
|
|
|
|
|
|
|
|
|
namespace Wabbajack.CacheServer
|
|
|
|
|
{
|
|
|
|
|
public class NexusCacheModule : NancyModule
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
public NexusCacheModule() : base("/")
|
|
|
|
|
{
|
|
|
|
|
Get("/v1/games/{GameName}/mods/{ModID}/files/{FileID}.json", HandleFileID);
|
|
|
|
|
Get("/v1/games/{GameName}/mods/{ModID}/files.json", HandleGetFiles);
|
|
|
|
|
Get("/v1/games/{GameName}/mods/{ModID}.json", HandleModInfo);
|
|
|
|
|
Get("/nexus_api_cache/{request}.json", HandleCacheCall);
|
|
|
|
|
Get("/nexus_api_cache", ListCache);
|
2019-11-22 00:41:01 +00:00
|
|
|
|
Get("/nexus_api_cache/update", UpdateCache);
|
2020-01-01 16:19:06 +00:00
|
|
|
|
Get("/nexus_api_cache/ingest/{Folder}", HandleIngestCache);
|
2019-11-22 00:41:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-01 17:11:48 +00:00
|
|
|
|
class UpdatedMod
|
|
|
|
|
{
|
|
|
|
|
public long mod_id;
|
|
|
|
|
public long latest_file_update;
|
|
|
|
|
public long latest_mod_activity;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-14 05:46:20 +00:00
|
|
|
|
public async Task<object> UpdateCache(object arg)
|
2019-11-22 00:41:01 +00:00
|
|
|
|
{
|
2019-12-07 03:50:50 +00:00
|
|
|
|
var api = await NexusApiClient.Get(Request.Headers["apikey"].FirstOrDefault());
|
2020-01-01 17:11:48 +00:00
|
|
|
|
|
|
|
|
|
var gameTasks = GameRegistry.Games.Values
|
|
|
|
|
.Where(game => game.NexusName != null)
|
|
|
|
|
.Select(async game =>
|
|
|
|
|
{
|
|
|
|
|
return (game,
|
|
|
|
|
mods: await api.Get<List<UpdatedMod>>(
|
|
|
|
|
$"https://api.nexusmods.com/v1/games/{game.NexusName}/mods/updated.json?period=1m"));
|
|
|
|
|
})
|
|
|
|
|
.Select(async rTask =>
|
|
|
|
|
{
|
|
|
|
|
var (game, mods) = await rTask;
|
|
|
|
|
return mods.Select(mod => new { game = game, mod = mod });
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
Utils.Log($"Getting update lits for {gameTasks.Count} games");
|
|
|
|
|
|
|
|
|
|
var purge = (await Task.WhenAll(gameTasks))
|
|
|
|
|
.SelectMany(i => i)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
Utils.Log($"Found {purge.Count} updated mods in the last month");
|
|
|
|
|
using (var queue = new WorkQueue())
|
|
|
|
|
{
|
|
|
|
|
var collected = await purge.Select(d =>
|
|
|
|
|
{
|
|
|
|
|
var a = d.mod.latest_file_update.AsUnixTime();
|
|
|
|
|
// Mod activity could hide files
|
|
|
|
|
var b = d.mod.latest_mod_activity.AsUnixTime();
|
|
|
|
|
|
|
|
|
|
return new {Game = d.game.NexusName, Date = (a > b ? a : b), ModId = d.mod.mod_id.ToString()};
|
|
|
|
|
}).PMap(queue, async t =>
|
|
|
|
|
{
|
|
|
|
|
var resultA = await Server.Config.NexusModInfos.Connect().DeleteManyAsync(f =>
|
|
|
|
|
f.Game == t.Game && f.ModId == t.ModId && f.LastCheckedUTC <= t.Date);
|
|
|
|
|
var resultB = await Server.Config.NexusModFiles.Connect().DeleteManyAsync(f =>
|
|
|
|
|
f.Game == t.Game && f.ModId == t.ModId && f.LastCheckedUTC <= t.Date);
|
|
|
|
|
var resultC = await Server.Config.NexusFileInfos.Connect().DeleteManyAsync(f =>
|
|
|
|
|
f.Game == t.Game && f.ModId == t.ModId && f.LastCheckedUTC <= t.Date);
|
|
|
|
|
|
|
|
|
|
return resultA.DeletedCount + resultB.DeletedCount + resultC.DeletedCount;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Utils.Log($"Purged {collected.Sum()} cache entries");
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-22 00:41:01 +00:00
|
|
|
|
return "Done";
|
2019-11-19 05:10:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string ListCache(object arg)
|
|
|
|
|
{
|
|
|
|
|
Utils.Log($"{DateTime.Now} - List Cache");
|
|
|
|
|
return String.Join("",
|
|
|
|
|
Directory.EnumerateFiles(NexusApiClient.LocalCacheDir)
|
|
|
|
|
.Select(f => new FileInfo(f))
|
|
|
|
|
.OrderByDescending(fi => fi.LastWriteTime)
|
|
|
|
|
.Select(fi =>
|
|
|
|
|
{
|
|
|
|
|
var decoded = Encoding.UTF8.GetString(Path.GetFileNameWithoutExtension(fi.Name).FromHex());
|
|
|
|
|
return $"{fi.LastWriteTime} \t {fi.Length.ToFileSizeString()} \t {decoded} \n";
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-01 16:19:06 +00:00
|
|
|
|
private async Task<Response> HandleModInfo(dynamic arg)
|
2019-11-19 05:10:07 +00:00
|
|
|
|
{
|
|
|
|
|
Utils.Log($"{DateTime.Now} - Mod Info - {arg.GameName}/{arg.ModID}/");
|
2020-01-01 16:19:06 +00:00
|
|
|
|
string gameName = arg.GameName;
|
|
|
|
|
string modId = arg.ModId;
|
|
|
|
|
var result = await Server.Config.NexusModInfos.Connect()
|
|
|
|
|
.FindOneAsync(info => info.Game == gameName && info.ModId == modId);
|
|
|
|
|
|
|
|
|
|
string method = "CACHED";
|
|
|
|
|
if (result == null)
|
|
|
|
|
{
|
|
|
|
|
var api = await NexusApiClient.Get(Request.Headers["apikey"].FirstOrDefault());
|
|
|
|
|
var path = $"/v1/{gameName}/mods/{modId}.json";
|
|
|
|
|
var body = await api.Get<ModInfo>(path);
|
|
|
|
|
result = new NexusCacheData<ModInfo>
|
|
|
|
|
{
|
|
|
|
|
Data = body,
|
|
|
|
|
Path = path,
|
|
|
|
|
Game = gameName,
|
|
|
|
|
ModId = modId
|
|
|
|
|
};
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Server.Config.NexusModInfos.Connect().InsertOneAsync(result);
|
|
|
|
|
}
|
|
|
|
|
catch (MongoWriteException)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
method = "NOT_CACHED";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Response response = result.Data.ToJSON();
|
|
|
|
|
response.Headers.Add("WABBAJACK_CACHE_FROM", method);
|
|
|
|
|
response.ContentType = "application/json";
|
|
|
|
|
return response;
|
2019-11-19 05:10:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-07 03:50:50 +00:00
|
|
|
|
private async Task<object> HandleFileID(dynamic arg)
|
2019-11-19 05:10:07 +00:00
|
|
|
|
{
|
|
|
|
|
Utils.Log($"{DateTime.Now} - File Info - {arg.GameName}/{arg.ModID}/{arg.FileID}");
|
2020-01-01 16:19:06 +00:00
|
|
|
|
string gameName = arg.GameName;
|
|
|
|
|
string modId = arg.ModId;
|
|
|
|
|
string fileId = arg.FileId;
|
|
|
|
|
var result = await Server.Config.NexusFileInfos.Connect()
|
|
|
|
|
.FindOneAsync(info => info.Game == gameName && info.ModId == modId && info.FileId == fileId);
|
|
|
|
|
|
|
|
|
|
string method = "CACHED";
|
|
|
|
|
if (result == null)
|
2019-11-19 05:10:07 +00:00
|
|
|
|
{
|
2020-01-01 16:19:06 +00:00
|
|
|
|
var api = await NexusApiClient.Get(Request.Headers["apikey"].FirstOrDefault());
|
|
|
|
|
var path = $"/v1/{gameName}/mods/{modId}/files/{fileId}.json";
|
|
|
|
|
var body = await api.Get<NexusFileInfo>(path);
|
|
|
|
|
result = new NexusCacheData<NexusFileInfo>
|
|
|
|
|
{
|
|
|
|
|
Data = body,
|
|
|
|
|
Path = path,
|
|
|
|
|
Game = gameName,
|
|
|
|
|
ModId = modId,
|
|
|
|
|
FileId = fileId
|
|
|
|
|
};
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Server.Config.NexusFileInfos.Connect().InsertOneAsync(result);
|
|
|
|
|
}
|
|
|
|
|
catch (MongoWriteException)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
method = "NOT_CACHED";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Response response = result.Data.ToJSON();
|
|
|
|
|
response.Headers.Add("WABBAJACK_CACHE_FROM", method);
|
|
|
|
|
response.ContentType = "application/json";
|
|
|
|
|
return response;
|
2019-11-19 05:10:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-07 03:50:50 +00:00
|
|
|
|
private async Task<object> HandleGetFiles(dynamic arg)
|
2019-11-19 05:10:07 +00:00
|
|
|
|
{
|
|
|
|
|
Utils.Log($"{DateTime.Now} - Mod Files - {arg.GameName} {arg.ModID}");
|
2020-01-01 16:19:06 +00:00
|
|
|
|
string gameName = arg.GameName;
|
|
|
|
|
string modId = arg.ModId;
|
|
|
|
|
var result = await Server.Config.NexusModFiles.Connect()
|
|
|
|
|
.FindOneAsync(info => info.Game == gameName && info.ModId == modId);
|
|
|
|
|
|
|
|
|
|
string method = "CACHED";
|
|
|
|
|
if (result == null)
|
|
|
|
|
{
|
|
|
|
|
var api = await NexusApiClient.Get(Request.Headers["apikey"].FirstOrDefault());
|
|
|
|
|
var path = $"/v1/{gameName}/mods/{modId}/files.json";
|
|
|
|
|
var body = await api.Get<NexusApiClient.GetModFilesResponse>(path);
|
|
|
|
|
result = new NexusCacheData<NexusApiClient.GetModFilesResponse>
|
|
|
|
|
{
|
|
|
|
|
Data = body,
|
|
|
|
|
Path = path,
|
|
|
|
|
Game = gameName,
|
|
|
|
|
ModId = modId
|
|
|
|
|
};
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Server.Config.NexusModFiles.Connect().InsertOneAsync(result);
|
|
|
|
|
}
|
|
|
|
|
catch (MongoWriteException)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
method = "NOT_CACHED";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Response response = result.Data.ToJSON();
|
|
|
|
|
response.Headers.Add("WABBAJACK_CACHE_FROM", method);
|
|
|
|
|
response.ContentType = "application/json";
|
|
|
|
|
return response;
|
2019-11-19 05:10:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-06 05:59:57 +00:00
|
|
|
|
private async Task<string> HandleCacheCall(dynamic arg)
|
2019-11-19 05:10:07 +00:00
|
|
|
|
{
|
2019-11-22 22:00:34 +00:00
|
|
|
|
try
|
2019-11-19 05:10:07 +00:00
|
|
|
|
{
|
2019-11-22 22:00:34 +00:00
|
|
|
|
string param = (string)arg.request;
|
|
|
|
|
var url = new Uri(Encoding.UTF8.GetString(param.FromHex()));
|
2019-11-19 05:10:07 +00:00
|
|
|
|
|
2020-01-01 16:19:06 +00:00
|
|
|
|
var client = new HttpClient();
|
|
|
|
|
var builder = new UriBuilder(url) {Host = "localhost", Port = Request.Url.Port ?? 8080, Scheme = "http"};
|
|
|
|
|
client.DefaultRequestHeaders.Add("apikey", Request.Headers["apikey"]);
|
|
|
|
|
return await client.GetStringAsync(builder.Uri.ToString());
|
2019-11-22 22:00:34 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Utils.Log(ex.ToString());
|
|
|
|
|
return "ERROR";
|
|
|
|
|
}
|
2019-11-19 05:10:07 +00:00
|
|
|
|
}
|
2020-01-01 16:19:06 +00:00
|
|
|
|
|
|
|
|
|
private async Task<string> HandleIngestCache(dynamic arg)
|
|
|
|
|
{
|
|
|
|
|
int count = 0;
|
|
|
|
|
int failed = 0;
|
|
|
|
|
|
|
|
|
|
using (var queue = new WorkQueue())
|
|
|
|
|
{
|
2020-01-01 22:38:21 +00:00
|
|
|
|
await Directory.EnumerateFiles(Path.Combine(Server.Config.Settings.TempDir, (string)arg.Folder)).PMap(queue,
|
2020-01-01 16:19:06 +00:00
|
|
|
|
async file =>
|
|
|
|
|
{
|
|
|
|
|
Utils.Log($"Ingesting {file}");
|
|
|
|
|
if (!file.EndsWith(".json")) return;
|
|
|
|
|
|
|
|
|
|
var fileInfo = new FileInfo(file);
|
|
|
|
|
count++;
|
|
|
|
|
|
|
|
|
|
var url = new Url(
|
|
|
|
|
Encoding.UTF8.GetString(Path.GetFileNameWithoutExtension(file).FromHex()));
|
|
|
|
|
var split = url.Path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
switch (split.Length)
|
|
|
|
|
{
|
|
|
|
|
case 5 when split[3] == "mods":
|
|
|
|
|
{
|
|
|
|
|
var body = file.FromJSON<ModInfo>();
|
|
|
|
|
|
|
|
|
|
var payload = new NexusCacheData<ModInfo>();
|
|
|
|
|
payload.Data = body;
|
|
|
|
|
payload.Game = split[2];
|
|
|
|
|
payload.Path = url.Path;
|
|
|
|
|
payload.ModId = body.mod_id;
|
|
|
|
|
payload.LastCheckedUTC = fileInfo.LastWriteTimeUtc;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Server.Config.NexusModInfos.Connect().InsertOneAsync(payload);
|
|
|
|
|
}
|
|
|
|
|
catch (MongoWriteException ex)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 6 when split[5] == "files.json":
|
|
|
|
|
{
|
|
|
|
|
var body = file.FromJSON<NexusApiClient.GetModFilesResponse>();
|
|
|
|
|
var payload = new NexusCacheData<NexusApiClient.GetModFilesResponse>();
|
|
|
|
|
payload.Path = url.Path;
|
|
|
|
|
payload.Data = body;
|
|
|
|
|
payload.Game = split[2];
|
|
|
|
|
payload.ModId = split[4];
|
|
|
|
|
payload.LastCheckedUTC = fileInfo.LastWriteTimeUtc;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Server.Config.NexusModFiles.Connect().InsertOneAsync(payload);
|
|
|
|
|
}
|
|
|
|
|
catch (MongoWriteException ex)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 7 when split[5] == "files":
|
|
|
|
|
{
|
|
|
|
|
var body = file.FromJSON<NexusFileInfo>();
|
|
|
|
|
var payload = new NexusCacheData<NexusFileInfo>();
|
|
|
|
|
payload.Data = body;
|
|
|
|
|
payload.Path = url.Path;
|
|
|
|
|
payload.Game = split[2];
|
|
|
|
|
payload.FileId = Path.GetFileNameWithoutExtension(split[6]);
|
|
|
|
|
payload.ModId = split[4];
|
|
|
|
|
payload.LastCheckedUTC = fileInfo.LastWriteTimeUtc;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Server.Config.NexusFileInfos.Connect().InsertOneAsync(payload);
|
|
|
|
|
}
|
|
|
|
|
catch (MongoWriteException ex)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
failed++;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $"Inserted {count} caches, {failed} failed";
|
|
|
|
|
}
|
2019-11-19 05:10:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|