using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Newtonsoft.Json;
namespace StardewModdingAPI.Framework.ModData
{
/// Handles access to SMAPI's internal mod metadata list.
internal class ModDatabase
{
/*********
** Properties
*********/
/// The underlying mod data records indexed by default display name.
private readonly IDictionary Records;
/// Get an update URL for an update key (if valid).
private readonly Func GetUpdateUrl;
/*********
** Public methods
*********/
/// Construct an empty instance.
public ModDatabase()
: this(new Dictionary(), key => null) { }
/// Construct an instance.
/// The underlying mod data records indexed by default display name.
/// Get an update URL for an update key (if valid).
public ModDatabase(IDictionary records, Func getUpdateUrl)
{
this.Records = records;
this.GetUpdateUrl = getUpdateUrl;
}
/// Get a parsed representation of the which match a given manifest.
/// The manifest to match.
public ParsedModDataRecord GetParsed(IManifest manifest)
{
// get raw record
if (!this.TryGetRaw(manifest, out string displayName, out ModDataRecord record))
return null;
// parse fields
ParsedModDataRecord parsed = new ParsedModDataRecord { DisplayName = displayName, DataRecord = record };
foreach (ModDataField field in record.GetFields().Where(field => field.IsMatch(manifest)))
{
switch (field.Key)
{
// update key
case ModDataFieldKey.UpdateKey:
parsed.UpdateKey = field.Value;
break;
// alternative URL
case ModDataFieldKey.AlternativeUrl:
parsed.AlternativeUrl = field.Value;
break;
// status
case ModDataFieldKey.Status:
parsed.Status = (ModStatus)Enum.Parse(typeof(ModStatus), field.Value, ignoreCase: true);
parsed.StatusUpperVersion = field.UpperVersion;
break;
// status reason phrase
case ModDataFieldKey.StatusReasonPhrase:
parsed.StatusReasonPhrase = field.Value;
break;
}
}
return parsed;
}
/// Get the display name for a given mod ID (if available).
/// The unique mod ID.
public string GetDisplayNameFor(string id)
{
return this.TryGetRaw(id, out string displayName, out ModDataRecord _)
? displayName
: null;
}
/// Get the mod page URL for a mod (if available).
/// The unique mod ID.
public string GetModPageUrlFor(string id)
{
// get raw record
if (!this.TryGetRaw(id, out string _, out ModDataRecord record))
return null;
// get update key
ModDataField updateKeyField = record.GetFields().FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey);
if (updateKeyField == null)
return null;
// get update URL
return this.GetUpdateUrl(updateKeyField.Value);
}
/*********
** Private models
*********/
/// Get a raw data record.
/// The mod ID to match.
/// The mod's default display name.
/// The raw mod record.
private bool TryGetRaw(string id, out string displayName, out ModDataRecord record)
{
foreach (var entry in this.Records)
{
if (entry.Value.ID != null && entry.Value.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase))
{
displayName = entry.Key;
record = entry.Value;
return true;
}
}
displayName = null;
record = null;
return false;
}
/// Get a raw data record.
/// The mod manifest whose fields to match.
/// The mod's default display name.
/// The raw mod record.
private bool TryGetRaw(IManifest manifest, out string displayName, out ModDataRecord record)
{
if (manifest != null)
{
foreach (var entry in this.Records)
{
displayName = entry.Key;
record = entry.Value;
// try main ID
if (record.ID != null && record.ID.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase))
return true;
// try former IDs
if (record.FormerIDs != null)
{
foreach (string part in record.FormerIDs.Split('|'))
{
// packed field snapshot
if (part.StartsWith("{"))
{
FieldSnapshot snapshot = JsonConvert.DeserializeObject(part);
bool isMatch =
(snapshot.ID == null || snapshot.ID.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase))
&& (snapshot.EntryDll == null || snapshot.EntryDll.Equals(manifest.EntryDll, StringComparison.InvariantCultureIgnoreCase))
&& (
snapshot.Author == null
|| snapshot.Author.Equals(manifest.Author, StringComparison.InvariantCultureIgnoreCase)
|| (manifest.ExtraFields.ContainsKey("Authour") && snapshot.Author.Equals(manifest.ExtraFields["Authour"].ToString(), StringComparison.InvariantCultureIgnoreCase))
)
&& (snapshot.Name == null || snapshot.Name.Equals(manifest.Name, StringComparison.InvariantCultureIgnoreCase));
if (isMatch)
return true;
}
// plain ID
else if (part.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase))
return true;
}
}
}
}
displayName = null;
record = null;
return false;
}
/*********
** Private models
*********/
/// A unique set of fields which identifies the mod.
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialisation.")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialisation.")]
private class FieldSnapshot
{
/*********
** Accessors
*********/
/// The unique mod ID (or null to ignore it).
public string ID { get; set; }
/// The entry DLL (or null to ignore it).
public string EntryDll { get; set; }
/// The mod name (or null to ignore it).
public string Name { get; set; }
/// The author name (or null to ignore it).
public string Author { get; set; }
}
}
}