Skip to content

Commit

Permalink
Merge interfaces into IModFeedItem
Browse files Browse the repository at this point in the history
  • Loading branch information
Sewer56 committed Oct 3, 2024
1 parent 406e290 commit 66c21c2
Show file tree
Hide file tree
Showing 11 changed files with 54 additions and 69 deletions.
22 changes: 22 additions & 0 deletions src/Networking/NexusMods.Networking.ModUpdates/IModFeedItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using NexusMods.Abstractions.NexusWebApi.Types.V2.Uid;
namespace NexusMods.Networking.ModUpdates;

/// <summary>
/// Represents an individual item from a 'mod feed'; with the 'mod feed' being
/// the result of an API call that returns one or more mods from the Nexus API.
/// (Either V1 or V2 API)
/// </summary>
public interface IModFeedItem
{
/// <summary>
/// Returns a unique identifier for the given item, based on the ID format
/// used in the NexusMods V2 API.
/// </summary>
public UidForMod GetModPageId();

/// <summary>
/// Retrieves the time the item was last updated.
/// This date is in UTC.
/// </summary>
public DateTime GetLastUpdatedDateUtc();
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
using NexusMods.Abstractions.NexusWebApi.DTOs;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Abstractions.NexusWebApi.Types.V2.Uid;
using NexusMods.Networking.ModUpdates.Traits;
namespace NexusMods.Networking.ModUpdates.Mixins;

/// <summary>
/// Implements the (V1) mod update API mixin.
/// </summary>
public readonly struct ModUpdateMixin : ICanGetUidForMod, ICanGetLastUpdatedTimestamp
public readonly struct ModFeedItemUpdateMixin : IModFeedItem
{
private readonly DateTime _lastUpdatedDate;
private readonly GameId _gameId;
private readonly ModId _modId;

/// <summary/>
public ModUpdateMixin(ModUpdate update, GameId gameId)
private ModFeedItemUpdateMixin(ModUpdate update, GameId gameId)
{
// Note(sewer): V2 doesn't have 'last file updated' field, so we have to use 'last mod page update' time.
// Well, this whole struct is, will be making that ticket to backend, and replace
Expand All @@ -27,13 +26,13 @@ public ModUpdateMixin(ModUpdate update, GameId gameId)
/// <summary>
/// Transforms the result of a V1 API call for mod updates into the Mixin.
/// </summary>
public static IEnumerable<ModUpdateMixin> FromUpdateResults(IEnumerable<ModUpdate> updates, GameId gameId) => updates.Select(update => new ModUpdateMixin(update, gameId));
public static IEnumerable<ModFeedItemUpdateMixin> FromUpdateResults(IEnumerable<ModUpdate> updates, GameId gameId) => updates.Select(update => new ModFeedItemUpdateMixin(update, gameId));

/// <inheritdoc />
public DateTime GetLastUpdatedDateUtc() => _lastUpdatedDate;

/// <inheritdoc />
public UidForMod GetUniqueId() => new()
public UidForMod GetModPageId() => new()
{
GameId = _gameId,
ModId = _modId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
using NexusMods.Abstractions.NexusModsLibrary;
using NexusMods.Abstractions.NexusWebApi.Types.V2.Uid;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.Networking.ModUpdates.Traits;
namespace NexusMods.Networking.ModUpdates.Mixins;

/// <summary>
/// Implements the MnemonicDB mod page mixin based on V2 API Results.
/// </summary>
public struct PageMetadataMixin : ICanGetUidForMod, ICanGetLastUpdatedTimestamp
public struct PageMetadataMixin : IModFeedItem
{
private readonly NexusModsModPageMetadata.ReadOnly _metadata;

private PageMetadataMixin(NexusModsModPageMetadata.ReadOnly metadata) => _metadata = metadata;

/// <inheritodc/>
public UidForMod GetUniqueId() => new()
public UidForMod GetModPageId() => new()
{
GameId = _metadata.Uid.GameId,
ModId = _metadata.Uid.ModId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Networking.ModUpdates.Traits;
namespace NexusMods.Networking.ModUpdates;

/// <summary>
Expand All @@ -12,7 +11,7 @@ namespace NexusMods.Networking.ModUpdates;
/// you to use mods and API responses which are sourced from multiple feeds (games),
/// as opposed to a single feed.
/// </summary>
public class MultiFeedCacheUpdater<TUpdateableItem> where TUpdateableItem : ICanGetLastUpdatedTimestamp, ICanGetUidForMod
public class MultiFeedCacheUpdater<TUpdateableItem> where TUpdateableItem : IModFeedItem
{
private readonly Dictionary<GameId, PerFeedCacheUpdater<TUpdateableItem>> _updaters;

Expand Down Expand Up @@ -41,7 +40,7 @@ public MultiFeedCacheUpdater(TUpdateableItem[] items, TimeSpan expiry)
var groupedList = new List<(GameId, List<TUpdateableItem>)>();
foreach (var item in items)
{
var gameId = item.GetUniqueId().GameId;
var gameId = item.GetModPageId().GameId;

// Get or Update List for this GameId.
var found = false;
Expand Down Expand Up @@ -73,15 +72,15 @@ public MultiFeedCacheUpdater(TUpdateableItem[] items, TimeSpan expiry)
/// include items corresponding to multiple feeds (games); the feed source
/// is automatically detected.
///
/// Wrap elements in a struct that implements <see cref="ICanGetLastUpdatedTimestamp"/>
/// and <see cref="ICanGetUidForMod"/> if necessary.
/// Wrap elements in a struct that implements <see cref="IModFeedItem"/>
/// and <see cref="IModFeedItem"/> if necessary.
/// </param>
public void Update<T>(IEnumerable<T> items) where T : ICanGetLastUpdatedTimestamp, ICanGetUidForMod
public void Update<T>(IEnumerable<T> items) where T : IModFeedItem
{
foreach (var item in items)
{
// Determine feed
var feed = item.GetUniqueId().GameId;
var feed = item.GetModPageId().GameId;

// The result may contain items from feeds which we are not tracking.
// For instance, results for other games. This is not an error, we
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Diagnostics;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Networking.ModUpdates.Private;
using NexusMods.Networking.ModUpdates.Traits;
namespace NexusMods.Networking.ModUpdates;

/// <summary>
Expand All @@ -11,8 +10,8 @@ namespace NexusMods.Networking.ModUpdates;
///
/// This API consists of the following:
///
/// 1. Input [Constructor]: A set of items with a 'last update time' (see <see cref="ICanGetLastUpdatedTimestamp"/>)
/// and a 'unique id' (see <see cref="ICanGetUidForMod"/>) that are relevant to the current 'feed' (game).
/// 1. Input [Constructor]: A set of items with a 'last update time' and a 'unique id'
/// (see <see cref="IModFeedItem"/>) that are relevant to the current 'feed' (game).
///
/// 2. Update [Method]: Submit results from API endpoint returning 'most recently updated mods for game'.
/// This updates the internal state of the <see cref="MultiFeedCacheUpdater{TUpdateableItem}"/>.
Expand All @@ -31,7 +30,7 @@ namespace NexusMods.Networking.ModUpdates;
/// The 'Feed' in the context of the Nexus App is the individual game's 'updated.json' endpoint;
/// i.e. a 'Game Mod Feed'
/// </remarks>
public class PerFeedCacheUpdater<TUpdateableItem> where TUpdateableItem : ICanGetLastUpdatedTimestamp, ICanGetUidForMod
public class PerFeedCacheUpdater<TUpdateableItem> where TUpdateableItem : IModFeedItem
{
private readonly TUpdateableItem[] _items;
private readonly Dictionary<ModId, int> _itemToIndex;
Expand Down Expand Up @@ -60,7 +59,7 @@ public PerFeedCacheUpdater(TUpdateableItem[] items, TimeSpan expiry)
_actions = new CacheUpdaterAction[items.Length];
_itemToIndex = new Dictionary<ModId, int>(items.Length);
for (var x = 0; x < _items.Length; x++)
_itemToIndex[_items[x].GetUniqueId().ModId] = x;
_itemToIndex[_items[x].GetModPageId().ModId] = x;

// Set the action to refresh cache for any mods which exceed max age.
var utcNow = DateTime.UtcNow;
Expand All @@ -78,21 +77,20 @@ public PerFeedCacheUpdater(TUpdateableItem[] items, TimeSpan expiry)
/// </summary>
/// <param name="items">
/// The items returned by the 'most recently updated mods for game' endpoint.
/// Wrap elements in a struct that implements <see cref="ICanGetLastUpdatedTimestamp"/>
/// and <see cref="ICanGetUidForMod"/> if necessary.
/// Wrap elements in a struct that implements <see cref="IModFeedItem"/> if necessary.
/// </param>
public void Update<T>(IEnumerable<T> items) where T : ICanGetLastUpdatedTimestamp, ICanGetUidForMod
public void Update<T>(IEnumerable<T> items) where T : IModFeedItem
{
foreach (var item in items)
UpdateSingleItem(item);
}

internal void UpdateSingleItem<T>(T item) where T : ICanGetLastUpdatedTimestamp, ICanGetUidForMod
internal void UpdateSingleItem<T>(T item) where T : IModFeedItem
{
// Try to get index of the item.
// Not all the items from the update feed are locally stored, thus we need to
// make sure we actually have this item.
if (!_itemToIndex.TryGetValue(item.GetUniqueId().ModId, out var index))
if (!_itemToIndex.TryGetValue(item.GetModPageId().ModId, out var index))
return;

var existingItem = _items[index];
Expand Down Expand Up @@ -147,8 +145,8 @@ private void DebugVerifyAllItemsAreFromSameGame()
{
if (_items.Length == 0) return;

var firstGameId = _items[0].GetUniqueId().GameId;
var allSame = _items.All(x => x.GetUniqueId().GameId == firstGameId);
var firstGameId = _items[0].GetModPageId().GameId;
var allSame = _items.All(x => x.GetModPageId().GameId == firstGameId);
if (!allSame)
throw new ArgumentException("All items must have the same game id", nameof(_items));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using NexusMods.Networking.ModUpdates.Traits;
namespace NexusMods.Networking.ModUpdates;

/// <summary>
/// Stores the result of updating the 'mod page' cache for a given feed or sets
/// of feeds.
/// </summary>
/// <typeparam name="TUpdateableItem">Wrapper for item supported by the cache updater.</typeparam>
public class PerFeedCacheUpdaterResult<TUpdateableItem> where TUpdateableItem : ICanGetLastUpdatedTimestamp, ICanGetUidForMod
public class PerFeedCacheUpdaterResult<TUpdateableItem> where TUpdateableItem : IModFeedItem
{
/// <summary>
/// This is a list of items that is 'out of date'.
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
using NexusMods.Abstractions.NexusWebApi.Types;
using NexusMods.Abstractions.NexusWebApi.Types.V2.Uid;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.Networking.ModUpdates;
using NexusMods.Networking.ModUpdates.Mixins;
using NexusMods.Networking.NexusWebApi;
using NexusMods.Networking.NexusWebApi.Extensions;
using StrawberryShake;
namespace NexusMods.Networking.ModUpdates;
namespace NexusMods.Networking.NexusWebApi;

/// <summary>
/// Utility class that encapsulates the logic for running the actual update check.
Expand All @@ -22,7 +22,7 @@ public static async Task<PerFeedCacheUpdaterResult<PageMetadataMixin>> CheckForM
{
// Extract all GameDomain(s)
var modPages = PageMetadataMixin.EnumerateDatabaseEntries(db).ToArray();
var gameIds = modPages.Select(x => (x.GetUniqueId().GameId)).Distinct().ToArray();
var gameIds = modPages.Select(x => (x.GetModPageId().GameId)).Distinct().ToArray();

// Note: The v1Timespan accounts for 1 month minus 5 minutes
// - We use 28 days because February is the shortest month at 28.
Expand All @@ -34,7 +34,7 @@ public static async Task<PerFeedCacheUpdaterResult<PageMetadataMixin>> CheckForM
{
// Note (sewer): We need to update to V2 stat.
var modUpdates = await apiClient.ModUpdatesAsync(gameId.ToGameDomain().Value, PastTime.Month);
var updateResults = ModUpdateMixin.FromUpdateResults(modUpdates.Data, gameId);
var updateResults = ModFeedItemUpdateMixin.FromUpdateResults(modUpdates.Data, gameId);
updater.Update(updateResults);
}

Expand All @@ -58,7 +58,7 @@ public static async Task UpdateModFilesForOutdatedPages(IDb db, ITransaction tx,
}
catch (Exception e)
{
var id = mixin.GetUniqueId();
var id = mixin.GetModPageId();
logger.LogError(e, "Failed to update metadata for Mod (GameID: {Page}, ModId: {ModId})", id.GameId, id.ModId);
}
}
Expand All @@ -73,7 +73,7 @@ public static async Task UpdateModFilesForOutdatedPages(IDb db, ITransaction tx,

private static async Task UpdateModPage(IDb db, ITransaction tx, INexusGraphQLClient gqlClient, CancellationToken cancellationToken, PageMetadataMixin mixin)
{
var uid = mixin.GetUniqueId();
var uid = mixin.GetModPageId();
var modIdString = uid.ModId.Value.ToString();
var gameIdString = uid.GameId.Value.ToString();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Abstractions.NexusWebApi.Types.V2.Uid;
using NexusMods.Networking.ModUpdates.Traits;
namespace NexusMods.Networking.ModUpdates.Tests.Helpers;

// Helper class to simulate updateable items
public class TestItem : ICanGetLastUpdatedTimestamp, ICanGetUidForMod
public class TestItem : IModFeedItem
{
public DateTime LastUpdated { get; set; }
public UidForMod Uid { get; set; }

public DateTime GetLastUpdatedDateUtc() => LastUpdated;
public UidForMod GetUniqueId() => Uid;
public UidForMod GetModPageId() => Uid;

// Helper method to create a test item
public static TestItem Create(uint gameId, uint modId, DateTime lastUpdated)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public async Task UpdatingModPageMetadata_ViaWebApi_ShouldWork()
// can only assert some 'general' knowledge.
// A single mod page got updated here.
updates.OutOfDateItems.Should().HaveCount(1);
var outOfDateMod = NexusModsModPageMetadata.FindByUid(Connection.Db, updates.OutOfDateItems.First().GetUniqueId()).First();
var outOfDateMod = NexusModsModPageMetadata.FindByUid(Connection.Db, updates.OutOfDateItems.First().GetModPageId()).First();
var outOfDateFileUid = outOfDateMod.Files.First().Uid;

// Fetch updated content for mod pages.
Expand Down

0 comments on commit 66c21c2

Please sign in to comment.