fix(serialization): Updates serialization flow & fixes serializing accounts (#521)

This commit is contained in:
Kamron Batman 2021-02-24 22:14:15 -08:00 • committed by GitHub
parent 78e3450671
commit 8d54d273a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 747 additions and 381 deletions

View file

@ -15,17 +15,17 @@ namespace Server.Accounting
public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0);
public static readonly TimeSpan InactiveDuration = TimeSpan.FromDays(180.0);
public static readonly TimeSpan EmptyInactiveDuration = TimeSpan.FromDays(30.0);
private readonly Mobile[] m_Mobiles;
private Mobile[] m_Mobiles;
private AccessLevel m_AccessLevel;
private List<AccountComment> m_Comments;
private PasswordProtectionAlgorithm m_PasswordAlgorithm;
private List<AccountTag> m_Tags;
private TimeSpan m_TotalGameTime;
private Timer m_YoungTimer;
private BufferWriter _saveBuffer;
public Account(string username, string password)
public Account(string username, string password) : this(Accounts.NewAccount)
{
Username = username;
@ -44,8 +44,33 @@ namespace Server.Accounting
Accounts.Add(this);
}
public Account(Serial serial)
{
Serial = serial;
var ourType = GetType();
TypeRef = Accounts.Types.IndexOf(ourType);
if (TypeRef == -1)
{
Accounts.Types.Add(ourType);
TypeRef = Accounts.Types.Count - 1;
}
}
public Account(XmlElement node)
{
Serial = Accounts.NewAccount;
var ourType = GetType();
TypeRef = Accounts.Types.IndexOf(ourType);
if (TypeRef == -1)
{
Accounts.Types.Add(ourType);
TypeRef = Accounts.Types.Count - 1;
}
Username = Utility.GetText(node["username"], "empty");
Enum.TryParse(Utility.GetText(node["passwordAlgorithm"], null), true, out m_PasswordAlgorithm);
@ -199,7 +224,7 @@ namespace Server.Accounting
/// <summary>
/// The date and time of when this account was created.
/// </summary>
public DateTime Created { get; }
public DateTime Created { get; private set; }
/// <summary>
/// Gets or sets the date and time when this account was last accessed.
@ -245,6 +270,146 @@ namespace Server.Accounting
}
}
BufferWriter ISerializable.SaveBuffer
{
get => _saveBuffer;
set => _saveBuffer = value;
}
public int TypeRef { get; private set; }
public Serial Serial { get; set; }
public void Deserialize(IGenericReader reader)
{
Username = reader.ReadString();
m_PasswordAlgorithm = (PasswordProtectionAlgorithm)reader.ReadInt();
Password = reader.ReadString();
m_AccessLevel = (AccessLevel)reader.ReadInt();
Flags = reader.ReadInt();
Created = reader.ReadDateTime();
LastLogin = reader.ReadDateTime();
TotalGold = reader.ReadInt();
TotalPlat = reader.ReadInt();
m_Mobiles = new Mobile[7];
var length = reader.ReadInt();
for (int i = 0; i < length; i++)
{
m_Mobiles[i] = reader.ReadEntity<Mobile>();
}
length = reader.ReadInt();
m_Comments = length > 0 ? new List<AccountComment>(length) : null;
for (int i = 0; i < length; i++)
{
m_Comments!.Add(new AccountComment(reader));
}
length = reader.ReadInt();
m_Tags = length > 0 ? new List<AccountTag>(length) : null;
for (int i = 0; i < length; i++)
{
m_Tags!.Add(new AccountTag(reader));
}
length = reader.ReadInt();
LoginIPs = new IPAddress[length];
for (int i = 0; i < length; i++)
{
if (IPAddress.TryParse(reader.ReadString(), out var address))
{
LoginIPs[i] = Utility.Intern(address);
}
}
length = reader.ReadInt();
IPRestrictions = new string[length];
for (int i = 0; i < length; i++)
{
IPRestrictions[i] = reader.ReadString();
}
for (var i = 0; i < m_Mobiles.Length; ++i)
{
if (m_Mobiles[i] != null)
{
m_Mobiles[i].Account = this;
}
}
var totalGameTime = reader.ReadTimeSpan();
if (totalGameTime == TimeSpan.Zero)
{
for (var i = 0; i < m_Mobiles.Length; i++)
{
if (m_Mobiles[i] is PlayerMobile m)
{
totalGameTime += m.GameTime;
}
}
}
m_TotalGameTime = totalGameTime;
if (Young)
{
CheckYoung();
}
}
public void Serialize(IGenericWriter writer)
{
writer.Write(Username);
writer.Write((int)m_PasswordAlgorithm);
writer.Write(Password);
writer.Write((int)m_AccessLevel);
writer.Write(Flags);
writer.Write(Created);
writer.Write(LastLogin);
writer.Write(TotalGold);
writer.Write(TotalPlat);
writer.Write(Count);
for (int i = 0; i < m_Mobiles.Length; i++)
{
var m = m_Mobiles[i];
if (m != null)
{
writer.Write(m);
}
}
var length = m_Comments?.Count ?? 0;
writer.Write(length);
for (int i = 0; i < length; i++)
{
m_Comments![i].Serialize(writer);
}
length = m_Tags?.Count ?? 0;
writer.Write(length);
for (int i = 0; i < length; i++)
{
m_Tags![i].Serialize(writer);
}
writer.Write(LoginIPs.Length);
for (int i = 0; i < LoginIPs.Length; i++)
{
writer.Write(LoginIPs[i].ToString());
}
writer.Write(IPRestrictions.Length);
for (int i = 0; i < IPRestrictions.Length; i++)
{
writer.Write(IPRestrictions[i]);
}
writer.Write(TotalGameTime);
}
/// <summary>
/// Deletes the account, all characters of the account, and all houses of those characters
/// </summary>
@ -277,9 +442,12 @@ namespace Server.Accounting
--AccountHandler.IPTable[LoginIPs[0]];
}
Accounts.Remove(Username);
Deleted = true;
Accounts.Remove(this);
}
public bool Deleted { get; private set; }
/// <summary>
/// Account username. Case insensitive validation.
/// </summary>
@ -784,7 +952,7 @@ namespace Server.Accounting
/// </summary>
/// <param name="node">The XmlElement from which to deserialize.</param>
/// <returns>String list. Value will never be null.</returns>
public static string[] LoadAccessCheck(XmlElement node)
private static string[] LoadAccessCheck(XmlElement node)
{
string[] stringList;
var accessCheck = node["accessCheck"];
@ -818,7 +986,7 @@ namespace Server.Accounting
/// </summary>
/// <param name="node">The XmlElement from which to deserialize.</param>
/// <returns>Address list. Value will never be null.</returns>
public static IPAddress[] LoadAddressList(XmlElement node)
private static IPAddress[] LoadAddressList(XmlElement node)
{
IPAddress[] list;
var addressList = node["addressList"];
@ -867,7 +1035,7 @@ namespace Server.Accounting
/// </summary>
/// <param name="node">The XmlElement instance from which to deserialize.</param>
/// <returns>Mobile list. Value will never be null.</returns>
public static Mobile[] LoadMobiles(XmlElement node)
private static Mobile[] LoadMobiles(XmlElement node)
{
var list = new Mobile[7];
var chars = node["chars"];
@ -905,7 +1073,7 @@ namespace Server.Accounting
/// </summary>
/// <param name="node">The XmlElement from which to deserialize.</param>
/// <returns>Comment list. Value will never be null.</returns>
public static List<AccountComment> LoadComments(XmlElement node)
private static List<AccountComment> LoadComments(XmlElement node)
{
List<AccountComment> list = null;
var comments = node["comments"];
@ -935,7 +1103,7 @@ namespace Server.Accounting
/// </summary>
/// <param name="node">The XmlElement from which to deserialize.</param>
/// <returns>Tag list. Value will never be null.</returns>
public static List<AccountTag> LoadTags(XmlElement node)
private static List<AccountTag> LoadTags(XmlElement node)
{
List<AccountTag> list = null;
var tags = node["tags"];

View file

@ -30,6 +30,17 @@ namespace Server.Accounting
m_Content = Utility.GetText(node, "");
}
/// <summary>
/// Deserializes an AccountComment instance.
/// </summary>
/// <param name="node">The deserialization reader</param>
public AccountComment(IGenericReader reader)
{
AddedBy = reader.ReadString();
LastModified = reader.ReadDateTime();
m_Content = reader.ReadString();
}
/// <summary>
/// A string representing who added this comment.
/// </summary>
@ -69,5 +80,16 @@ namespace Server.Accounting
xml.WriteEndElement();
}
/// <summary>
/// Serializes this AccountComment instance.
/// </summary>
/// <param name="xml">The serialization writer.</param>
public void Serialize(IGenericWriter writer)
{
writer.Write(AddedBy ?? "empty");
writer.Write(LastModified);
writer.Write(m_Content);
}
}
}

View file

@ -25,6 +25,16 @@ namespace Server.Accounting
Value = Utility.GetText(node, "");
}
/// <summary>
/// Deserializes an AccountTag instance .
/// </summary>
/// <param name="node">The deserialization reader</param>
public AccountTag(IGenericReader reader)
{
Name = reader.ReadString();
Value = reader.ReadString();
}
/// <summary>
/// Gets or sets the name of this tag.
/// </summary>
@ -46,5 +56,15 @@ namespace Server.Accounting
xml.WriteString(Value);
xml.WriteEndElement();
}
/// <summary>
/// Serializes this AccountTag instance to an XmlTextWriter.
/// </summary>
/// <param name="xml">The serialization writer.</param>
public void Serialize(IGenericWriter writer)
{
writer.Write(Name ?? "empty");
writer.Write(Value);
}
}
}

View file

@ -7,50 +7,92 @@ namespace Server.Accounting
{
public static class Accounts
{
private static Dictionary<string, IAccount> m_Accounts = new();
private static readonly Dictionary<string, IAccount> _accountsByName = new(32, StringComparer.OrdinalIgnoreCase);
private static Dictionary<Serial, IAccount> _accountsById = new(32);
private static Serial _lastAccount;
internal static List<Type> Types { get; } = new();
static Accounts()
private static void OutOfMemory(string message) => throw new OutOfMemoryException(message);
public static Serial NewAccount
{
get
{
uint last = _lastAccount;
for (uint i = 0; i < uint.MaxValue; i++)
{
last++;
if (FindAccount(last) == null)
{
return _lastAccount = last;
}
}
OutOfMemory("No serials left to allocate for accounts");
return Serial.MinusOne;
}
}
public static int Count => m_Accounts.Count;
public static int Count => _accountsByName.Count;
public static void Configure()
public static void Configure() =>
Persistence.Register(Serialize, WriteSnapshot, Deserialize);
internal static void Serialize() =>
EntityPersistence.SaveEntities(_accountsById.Values, account => account.Serialize());
internal static void WriteSnapshot(string basePath)
{
EventSink.WorldLoad += Load;
EventSink.WorldSave += Save;
IIndexInfo<Serial> indexInfo = new EntityTypeIndex("Accounts");
EntityPersistence.WriteEntities(indexInfo, _accountsById, Types, basePath, out _);
}
public static IEnumerable<IAccount> GetAccounts() => m_Accounts.Values;
public static IEnumerable<IAccount> GetAccounts() => _accountsByName.Values;
public static IAccount GetAccount(string username)
{
m_Accounts.TryGetValue(username, out var a);
_accountsByName.TryGetValue(username, out var a);
return a;
}
public static void Add(IAccount a)
{
m_Accounts[a.Username] = a;
_accountsByName[a.Username] = a;
_accountsById[a.Serial] = a;
}
public static void Remove(string username)
public static void Remove(IAccount a)
{
m_Accounts.Remove(username);
_accountsByName.Remove(a.Username);
_accountsById.Remove(a.Serial);
}
public static void Load()
internal static void Deserialize(string path)
{
m_Accounts = new Dictionary<string, IAccount>(32, StringComparer.OrdinalIgnoreCase);
var filePath = Path.Combine(path, "Accounts", "accounts.xml");
var filePath = Path.Combine("Saves/Accounts", "accounts.xml");
if (!File.Exists(filePath))
// Backward Compatibility
if (File.Exists(filePath))
{
DeserializeXml(filePath);
return;
}
IIndexInfo<Serial> indexInfo = new EntityTypeIndex("Accounts");
_accountsById = EntityPersistence.LoadIndex(path, indexInfo, out List<EntityIndex<IAccount>> accounts);
EntityPersistence.LoadData(path, indexInfo, accounts);
foreach (var a in _accountsById.Values)
{
_accountsByName[a.Username] = a;
}
}
private static void DeserializeXml(string filePath)
{
var doc = new XmlDocument();
doc.Load(filePath);
@ -74,29 +116,10 @@ namespace Server.Accounting
}
}
public static void Save()
public static IAccount FindAccount(Serial serial)
{
AssemblyHandler.EnsureDirectory("Saves/Accounts");
var filePath = Path.Combine("Saves/Accounts", "accounts.xml");
using var op = new StreamWriter(filePath);
var xml = new XmlTextWriter(op) { Formatting = Formatting.Indented, IndentChar = '\t', Indentation = 1 };
xml.WriteStartDocument(true);
xml.WriteStartElement("accounts");
xml.WriteAttributeString("count", m_Accounts.Count.ToString());
foreach (Account a in GetAccounts())
{
a.Save(xml);
}
xml.WriteEndElement();
xml.Close();
_accountsById.TryGetValue(serial, out var account);
return account;
}
}
}