Files
gaseous-server/gaseous-server/Classes/Metadata/Games.cs
2024-08-07 23:57:38 +10:00

580 lines
24 KiB
C#

using System;
using System.Data;
using IGDB;
using IGDB.Models;
namespace gaseous_server.Classes.Metadata
{
public class Games
{
const string fieldList = "fields age_ratings,aggregated_rating,aggregated_rating_count,alternative_names,artworks,bundles,category,checksum,collections,cover,created_at,dlcs,expanded_games,expansions,external_games,first_release_date,follows,forks,franchise,franchises,game_engines,game_localizations,game_modes,genres,hypes,involved_companies,keywords,language_supports,multiplayer_modes,name,parent_game,platforms,player_perspectives,ports,rating,rating_count,release_dates,remakes,remasters,screenshots,similar_games,slug,standalone_expansions,status,storyline,summary,tags,themes,total_rating,total_rating_count,updated_at,url,version_parent,version_title,videos,websites;";
public Games()
{
}
public class InvalidGameId : Exception
{
public InvalidGameId(long Id) : base("Unable to find Game by id " + Id)
{ }
}
public static Game? GetGame(long Id, bool getAllMetadata, bool followSubGames, bool forceRefresh)
{
if (Id == 0)
{
Game returnValue = new Game();
if (Storage.GetCacheStatus("Game", 0) == Storage.CacheStatus.NotPresent)
{
returnValue = new Game
{
Id = 0,
Name = "Unknown Title",
Slug = "Unknown"
};
Storage.NewCacheValue(returnValue);
return returnValue;
}
else
{
return Storage.GetCacheValue<Game>(returnValue, "id", 0);
}
}
else
{
Task<Game> RetVal = _GetGame(SearchUsing.id, Id, getAllMetadata, followSubGames, forceRefresh);
return RetVal.Result;
}
}
public static Game GetGame(string Slug, bool getAllMetadata, bool followSubGames, bool forceRefresh)
{
Task<Game> RetVal = _GetGame(SearchUsing.slug, Slug, getAllMetadata, followSubGames, forceRefresh);
return RetVal.Result;
}
public static Game GetGame(DataRow dataRow)
{
return Storage.BuildCacheObject<Game>(new Game(), dataRow);
}
private static async Task<Game> _GetGame(SearchUsing searchUsing, object searchValue, bool getAllMetadata = true, bool followSubGames = false, bool forceRefresh = false)
{
// check database first
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
if (searchUsing == SearchUsing.id)
{
cacheStatus = Storage.GetCacheStatus("Game", (long)searchValue);
}
else
{
cacheStatus = Storage.GetCacheStatus("Game", (string)searchValue);
}
if (forceRefresh == true)
{
if (cacheStatus == Storage.CacheStatus.Current) { cacheStatus = Storage.CacheStatus.Expired; }
}
string WhereClause = "";
string searchField = "";
switch (searchUsing)
{
case SearchUsing.id:
WhereClause = "where id = " + searchValue;
searchField = "id";
break;
case SearchUsing.slug:
WhereClause = "where slug = \"" + searchValue + "\"";
searchField = "slug";
break;
default:
throw new Exception("Invalid search type");
}
Game returnValue = new Game();
switch (cacheStatus)
{
case Storage.CacheStatus.NotPresent:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue);
UpdateSubClasses(returnValue, getAllMetadata, followSubGames, forceRefresh);
return returnValue;
case Storage.CacheStatus.Expired:
try
{
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true);
UpdateSubClasses(returnValue, getAllMetadata, followSubGames, forceRefresh);
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
returnValue = Storage.GetCacheValue<Game>(returnValue, searchField, searchValue);
}
return returnValue;
case Storage.CacheStatus.Current:
returnValue = Storage.GetCacheValue<Game>(returnValue, searchField, searchValue);
UpdateSubClasses(returnValue, false, false, false);
return returnValue;
default:
throw new Exception("How did you get here?");
}
}
private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames, bool forceRefresh)
{
// required metadata
// if (Game.Cover != null)
// {
// try
// {
// Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
// }
// catch (Exception ex)
// {
// Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex);
// }
// }
if (Game.Genres != null)
{
foreach (long GenreId in Game.Genres.Ids)
{
Genre GameGenre = Genres.GetGenres(GenreId);
}
}
if (Game.GameModes != null)
{
foreach (long gameModeId in Game.GameModes.Ids)
{
GameMode gameMode = GameModes.GetGame_Modes(gameModeId);
}
}
if (Game.MultiplayerModes != null)
{
foreach (long multiplayerModeId in Game.MultiplayerModes.Ids)
{
MultiplayerMode multiplayerMode = MultiplayerModes.GetGame_MultiplayerModes(multiplayerModeId);
}
}
if (Game.PlayerPerspectives != null)
{
foreach (long PerspectiveId in Game.PlayerPerspectives.Ids)
{
PlayerPerspective GamePlayPerspective = PlayerPerspectives.GetGame_PlayerPerspectives(PerspectiveId);
}
}
if (Game.Themes != null)
{
foreach (long ThemeId in Game.Themes.Ids)
{
Theme GameTheme = Themes.GetGame_Themes(ThemeId);
}
}
if (Game.AgeRatings != null)
{
foreach (long AgeRatingId in Game.AgeRatings.Ids)
{
AgeRating GameAgeRating = AgeRatings.GetAgeRatings(AgeRatingId);
}
}
AgeGroups.GetAgeGroup(Game);
if (Game.ReleaseDates != null)
{
foreach (long ReleaseDateId in Game.ReleaseDates.Ids)
{
ReleaseDate GameReleaseDate = ReleaseDates.GetReleaseDates(ReleaseDateId);
}
}
// optional metadata - usually downloaded as needed
if (getAllMetadata == true)
{
if (Game.AlternativeNames != null)
{
foreach (long AlternativeNameId in Game.AlternativeNames.Ids)
{
AlternativeName GameAlternativeName = AlternativeNames.GetAlternativeNames(AlternativeNameId);
}
}
if (Game.Artworks != null)
{
foreach (long ArtworkId in Game.Artworks.Ids)
{
try
{
Artwork GameArtwork = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch artwork id: " + ArtworkId, ex);
}
}
}
if (followSubGames)
{
List<long> gamesToFetch = new List<long>();
if (Game.Bundles != null) { gamesToFetch.AddRange(Game.Bundles.Ids); }
if (Game.Dlcs != null) { gamesToFetch.AddRange(Game.Dlcs.Ids); }
if (Game.Expansions != null) { gamesToFetch.AddRange(Game.Expansions.Ids); }
if (Game.ParentGame != null) { gamesToFetch.Add((long)Game.ParentGame.Id); }
//if (Game.SimilarGames != null) { gamesToFetch.AddRange(Game.SimilarGames.Ids); }
if (Game.StandaloneExpansions != null) { gamesToFetch.AddRange(Game.StandaloneExpansions.Ids); }
if (Game.VersionParent != null) { gamesToFetch.Add((long)Game.VersionParent.Id); }
foreach (long gameId in gamesToFetch)
{
Game relatedGame = GetGame(gameId, false, true, false);
}
}
if (Game.Collection != null)
{
Collection GameCollection = Collections.GetCollections(Game.Collection.Id);
}
if (Game.ExternalGames != null)
{
foreach (long ExternalGameId in Game.ExternalGames.Ids)
{
ExternalGame GameExternalGame = ExternalGames.GetExternalGames(ExternalGameId);
}
}
if (Game.Franchise != null)
{
Franchise GameFranchise = Franchises.GetFranchises(Game.Franchise.Id);
}
if (Game.Franchises != null)
{
foreach (long FranchiseId in Game.Franchises.Ids)
{
Franchise GameFranchise = Franchises.GetFranchises(FranchiseId);
}
}
if (Game.InvolvedCompanies != null)
{
foreach (long involvedCompanyId in Game.InvolvedCompanies.Ids)
{
InvolvedCompany involvedCompany = InvolvedCompanies.GetInvolvedCompanies(involvedCompanyId);
}
}
if (Game.Platforms != null)
{
foreach (long PlatformId in Game.Platforms.Ids)
{
Platform GamePlatform = Platforms.GetPlatform(PlatformId);
}
}
if (Game.Screenshots != null)
{
foreach (long ScreenshotId in Game.Screenshots.Ids)
{
try
{
Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch screenshot id: " + ScreenshotId, ex);
}
}
}
if (Game.Videos != null)
{
foreach (long GameVideoId in Game.Videos.Ids)
{
GameVideo gameVideo = GamesVideos.GetGame_Videos(GameVideoId);
}
}
}
}
private enum SearchUsing
{
id,
slug
}
private static async Task<Game> GetObjectFromServer(string WhereClause)
{
// get Game metadata
Communications comms = new Communications();
var results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, fieldList, WhereClause);
var result = results.First();
// add artificial unknown platform mapping
List<long> platformIds = new List<long>();
platformIds.Add(0);
if (result.Platforms != null)
{
if (result.Platforms.Ids != null)
{
platformIds.AddRange(result.Platforms.Ids.ToList());
}
}
result.Platforms = new IdentitiesOrValues<Platform>(
ids: platformIds.ToArray<long>()
);
// get cover art from parent if this has no cover
if (result.Cover == null)
{
if (result.ParentGame != null)
{
if (result.ParentGame.Id != null)
{
Logging.Log(Logging.LogType.Information, "Game Metadata", "Game has no cover art, fetching cover art from parent game");
Game parentGame = GetGame((long)result.ParentGame.Id, false, false, false);
result.Cover = parentGame.Cover;
}
}
}
// get missing metadata from parent if this is a port
if (result.Category == Category.Port)
{
if (result.Summary == null)
{
if (result.ParentGame != null)
{
if (result.ParentGame.Id != null)
{
Logging.Log(Logging.LogType.Information, "Game Metadata", "Game has no summary, fetching summary from parent game");
Game parentGame = GetGame((long)result.ParentGame.Id, false, false, false);
result.Summary = parentGame.Summary;
}
}
}
}
return result;
}
public static void AssignAllGamesToPlatformIdZero()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM Game;";
DataTable gamesTable = db.ExecuteCMD(sql);
foreach (DataRow gameRow in gamesTable.Rows)
{
sql = "DELETE FROM Relation_Game_Platforms WHERE PlatformsId = 0 AND GameId = @Id; INSERT INTO Relation_Game_Platforms (GameId, PlatformsId) VALUES (@Id, 0);";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("Id", (long)gameRow["Id"]);
db.ExecuteCMD(sql, dbDict);
}
}
private static bool AllowNoPlatformSearch = false;
public static Game[] SearchForGame(string SearchString, long PlatformId, SearchType searchType)
{
// search local first
Logging.Log(Logging.LogType.Information, "Game Search", "Attempting local search of type '" + searchType.ToString() + "' for " + SearchString);
Task<Game[]> games = _SearchForGameDatabase(SearchString, PlatformId, searchType);
if (games.Result.Length == 0)
{
// fall back to online search
Logging.Log(Logging.LogType.Information, "Game Search", "Falling back to remote search of type '" + searchType.ToString() + "' for " + SearchString);
games = _SearchForGameRemote(SearchString, PlatformId, searchType);
}
return games.Result;
}
private static async Task<Game[]> _SearchForGameDatabase(string SearchString, long PlatformId, SearchType searchType)
{
string whereClause = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
bool allowSearch = true;
switch (searchType)
{
case SearchType.searchNoPlatform:
whereClause = "MATCH(`Name`) AGAINST (@gamename)";
dbDict.Add("platformid", PlatformId);
dbDict.Add("gamename", SearchString);
allowSearch = AllowNoPlatformSearch;
break;
case SearchType.search:
whereClause = "PlatformsId = @platformid AND MATCH(`Name`) AGAINST (@gamename)";
dbDict.Add("platformid", PlatformId);
dbDict.Add("gamename", SearchString);
break;
case SearchType.wherefuzzy:
whereClause = "PlatformsId = @platformid AND `Name` LIKE @gamename";
dbDict.Add("platformid", PlatformId);
dbDict.Add("gamename", "%" + SearchString + "%");
break;
case SearchType.where:
whereClause = "PlatformsId = @platformid AND `Name` = @gamename";
dbDict.Add("platformid", PlatformId);
dbDict.Add("gamename", SearchString);
break;
}
string sql = "SELECT Game.Id, Game.`Name`, Game.Slug, Relation_Game_Platforms.PlatformsId AS PlatformsId, Game.Summary FROM gaseous.Game JOIN Relation_Game_Platforms ON Game.Id = Relation_Game_Platforms.GameId WHERE " + whereClause + ";";
// get Game metadata
Game[]? results = new Game[0];
if (allowSearch == true)
{
List<Game> searchResults = new List<Game>();
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
DataTable data = db.ExecuteCMD(sql, dbDict);
foreach (DataRow row in data.Rows)
{
Game game = new Game
{
Id = (long)row["Id"],
Name = (string)Common.ReturnValueIfNull(row["Name"], ""),
Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""),
Summary = (string)Common.ReturnValueIfNull(row["Summary"], "")
};
searchResults.Add(game);
}
results = searchResults.ToArray();
}
return results;
}
private static async Task<Game[]> _SearchForGameRemote(string SearchString, long PlatformId, SearchType searchType)
{
string searchBody = "";
string searchFields = "fields id,name,slug,platforms,summary; ";
bool allowSearch = true;
switch (searchType)
{
case SearchType.searchNoPlatform:
searchBody = "search \"" + SearchString + "\"; ";
allowSearch = AllowNoPlatformSearch;
break;
case SearchType.search:
searchBody = "search \"" + SearchString + "\"; where platforms = (" + PlatformId + ");";
break;
case SearchType.wherefuzzy:
searchBody = "where platforms = (" + PlatformId + ") & name ~ *\"" + SearchString + "\"*;";
break;
case SearchType.where:
searchBody = "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";";
break;
}
// check search cache
Game[]? games = Communications.GetSearchCache<Game[]?>(searchFields, searchBody);
if (games == null)
{
// cache miss
// get Game metadata
Communications comms = new Communications();
Game[]? results = new Game[0];
if (allowSearch == true)
{
results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, searchFields, searchBody);
Communications.SetSearchCache<Game[]?>(searchFields, searchBody, results);
}
return results;
}
else
{
return games.ToArray();
}
}
public static List<KeyValuePair<long, string>> GetAvailablePlatforms(long GameId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT DISTINCT Games_Roms.PlatformId, Platform.`Name` FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE Games_Roms.GameId = @gameid ORDER BY Platform.`Name`;";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("gameid", GameId);
DataTable data = db.ExecuteCMD(sql, dbDict);
List<KeyValuePair<long, string>> platforms = new List<KeyValuePair<long, string>>();
foreach (DataRow row in data.Rows)
{
KeyValuePair<long, string> valuePair = new KeyValuePair<long, string>((long)row["PlatformId"], (string)row["Name"]);
platforms.Add(valuePair);
}
return platforms;
}
public enum SearchType
{
where = 0,
wherefuzzy = 1,
search = 2,
searchNoPlatform = 3
}
public class MinimalGameItem
{
public MinimalGameItem()
{
}
public MinimalGameItem(Game gameObject)
{
this.Id = gameObject.Id;
this.Name = gameObject.Name;
this.Slug = gameObject.Slug;
this.TotalRating = gameObject.TotalRating;
this.TotalRatingCount = gameObject.TotalRatingCount;
this.Cover = gameObject.Cover;
this.Artworks = gameObject.Artworks;
this.FirstReleaseDate = gameObject.FirstReleaseDate;
// compile age ratings
this.AgeRatings = new List<AgeRating>();
if (gameObject.AgeRatings != null)
{
foreach (long ageRatingId in gameObject.AgeRatings.Ids)
{
AgeRating? rating = Classes.Metadata.AgeRatings.GetAgeRatings(ageRatingId);
if (rating != null)
{
this.AgeRatings.Add(rating);
}
}
}
}
public long? Id { get; set; }
public long Index { get; set; }
public string Name { get; set; }
public string Slug { get; set; }
public double? TotalRating { get; set; }
public int? TotalRatingCount { get; set; }
public bool HasSavedGame { get; set; } = false;
public bool IsFavourite { get; set; } = false;
public DateTimeOffset? FirstReleaseDate { get; set; }
public IGDB.IdentityOrValue<IGDB.Models.Cover> Cover { get; set; }
public IGDB.IdentitiesOrValues<IGDB.Models.Artwork> Artworks { get; set; }
public List<IGDB.Models.AgeRating> AgeRatings { get; set; }
}
}
}