using System.Globalization; using log4net; using SQLiteEF; using SteamApiWrapper.ReturnTypes; using SteamGame = SteamApiWrapper.ReturnTypes.Game; using Player = SQLiteEF.Player; using SteamPlayer = SteamApiWrapper.ReturnTypes.Player; using Steam = SteamApiWrapper.SteamApiWrapper; namespace API; public class Tracker : IDisposable { private readonly Thread _trackerThread; private bool _running = true; private ILog Log { get; } = LogManager.GetLogger(typeof(Tracker)); private TimeSpan Interval { get; init; } public Tracker(IServiceProvider serviceProvider, IConfiguration configuration) { Interval = TimeSpan.Parse(configuration.GetValue("UpdateInterval")!, CultureInfo.InvariantCulture); _trackerThread = new (TrackerLoop); _trackerThread.Start(serviceProvider); } internal void UpdatePlayers(Context context) { Log.Info("Updating Players"); Player[] dbPlayers = context.Players.ToArray(); SteamPlayer[] summaries = Steam.GetPlayerSummaries(dbPlayers.Select(p => p.SteamId).ToArray()); foreach (Player dbPlayer in dbPlayers) { if(summaries.All(summaryPlayer => summaryPlayer.steamid != dbPlayer.SteamId)) continue; SteamPlayer summaryPlayer = summaries.First(summaryPlayer => summaryPlayer.steamid == dbPlayer.SteamId); try { dbPlayer.Name = summaryPlayer.personaname; dbPlayer.AvatarUrl = summaryPlayer.avatar; dbPlayer.ProfileUrl = summaryPlayer.profileurl; dbPlayer.UpdatedAt = DateTime.UtcNow; context.SaveChanges(); } catch (Exception e) { Log.Error(e); } } } internal void UpdatePlayer(Context context, Player player) { SteamPlayer[] summaries = Steam.GetPlayerSummaries([player.SteamId]); if(summaries.All(summaryPlayer => summaryPlayer.steamid != player.SteamId)) return; SteamPlayer summaryPlayer = summaries.First(summaryPlayer => summaryPlayer.steamid == player.SteamId); try { player.Name = summaryPlayer.personaname; player.AvatarUrl = summaryPlayer.avatar; player.ProfileUrl = summaryPlayer.profileurl; player.UpdatedAt = DateTime.UtcNow; context.SaveChanges(); } catch (Exception e) { Log.Error(e); } } internal void UpdateOwnedGames(Context context) { Log.Info("Updating Owned Games"); Player[] players = context.Players.ToArray(); foreach (Player player in players) { UpdateOwnedGamesPlayer(context, player); } } internal void UpdateOwnedGamesPlayer(Context context, Player player) { Log.Debug($"Updating owned games for player {player}"); SteamGame[] ownedGames = Steam.GetOwnedGames(player.SteamId); foreach (SteamGame ownedGame in ownedGames) { if (context.Games.Find(ownedGame.appid) is not { } game) { game = new(ownedGame.appid, ownedGame.name); context.Games.Add(game); } if (!player.Games.Contains(game)) { context.Entry(player).Collection(p => p.Games).Load(); player.Games.Add(game); context.Entry(game).Collection(g => g.PlayedBy).Load(); game.PlayedBy.Add(player); context.Entry(player).Collection(p => p.TrackedTimes).Load(); player.TrackedTimes.Add(new (game, player, ownedGame.playtime_forever)); } try { context.SaveChanges(); } catch (Exception e) { Log.Error(e); } } } internal void UpdateGameTimes(Context context) { Log.Info("Updating Game Times"); Player[] players = context.Players.ToArray(); foreach (Player player in players) { UpdateGameTimesPlayer(context, player); } } internal void UpdateGameTimesPlayer(Context context, Player player) { Log.Debug($"Updating game times for player {player}"); GetRecentlyPlayedGames recentlyPlayed = Steam.GetRecentlyPlayedGames(player.SteamId); foreach (SteamGame recentlyPlayedGame in recentlyPlayed.games) { if (context.Games.Find(recentlyPlayedGame.appid) is not { } game) { game = new(recentlyPlayedGame.appid, recentlyPlayedGame.name); context.Games.Add(game); } if (!player.Games.Contains(game)) { context.Entry(player).Collection(p => p.Games).Load(); player.Games.Add(game); context.Entry(game).Collection(g => g.PlayedBy).Load(); game.PlayedBy.Add(player); } context.Entry(player).Collection(p => p.TrackedTimes).Load(); player.TrackedTimes.Add(new (game, player, recentlyPlayedGame.playtime_forever)); try { context.SaveChanges(); } catch (Exception e) { Log.Error(e); } } } private void TrackerLoop(object? obj) { if (obj is not IServiceProvider serviceProvider) { Log.Fatal("Tracker loop wasn't started."); return; } while (_running) { IServiceScope scope = serviceProvider.CreateScope(); Context context = scope.ServiceProvider.GetRequiredService(); UpdatePlayers(context); UpdateOwnedGames(context); UpdateGameTimes(context); scope.Dispose(); try { Thread.Sleep(Interval); } catch (ThreadInterruptedException) { Log.Debug("Thread interrupted"); } catch (ThreadAbortException) { _running = false; } } Log.Info("Thread exited"); } public void ForceLoop() { _trackerThread.Interrupt(); } public void Dispose() { _running = false; _trackerThread.Interrupt(); } }