Merge pull request #3 from CJ-SPT/Util-Impls

Time and Hash util implementations, partial RandomUtil impl, fixes namespaces
This commit is contained in:
clodanSPT
2025-01-06 15:04:39 +00:00
committed by GitHub
28 changed files with 483 additions and 47 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Annotations;
namespace Core.Annotations;
[AttributeUsage(AttributeTargets.Class)]
public class Injectable(InjectionType injectionType = InjectionType.Scoped, Type? type = null) : Attribute
+2 -2
View File
@@ -1,6 +1,6 @@
using Types.Annotations;
using Core.Annotations;
namespace Types.Context;
namespace Core.Context;
[Injectable(InjectionType.Singleton)]
public class ApplicationContext
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Context;
namespace Core.Context;
public class ContextVariable(object value, ContextVariableType contextVariableType)
{
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Context;
namespace Core.Context;
public enum ContextVariableType
{
-1
View File
@@ -5,7 +5,6 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<RootNamespace>Types</RootNamespace>
</PropertyGroup>
<ItemGroup>
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Models.Config;
namespace Core.Models.Config;
public class HttpConfig
{
+1 -1
View File
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Types.Models.Eft.Common.Tables;
namespace Core.Models.Eft.Common.Tables;
public class BotBase
{
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Models.Eft.Common.Tables;
namespace Core.Models.Eft.Common.Tables;
public class BotCore
{
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Models.Eft.Common.Tables;
namespace Core.Models.Eft.Common.Tables;
public class BotType
{
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Models.Enums;
namespace Core.Models.Enums;
public enum ConfigTypes
{
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Models.Logging;
namespace Core.Models.Logging;
public enum LogBackgroundColor
{
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Models.Logging;
namespace Core.Models.Logging;
public enum LogTextColor
{
+1 -1
View File
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Types.Models.Spt.Bots;
namespace Core.Models.Spt.Bots;
public class Bots
{
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Models.Spt.Server;
namespace Core.Models.Spt.Server;
public class DatabaseTables
{
+2 -2
View File
@@ -1,6 +1,6 @@
using Types.Models.Logging;
using Core.Models.Logging;
namespace Types.Models.Utils;
namespace Core.Models.Utils;
public interface ILogger
{
+2 -2
View File
@@ -1,6 +1,6 @@
using Types.Annotations;
using Core.Annotations;
namespace Types.Servers;
namespace Core.Servers;
[Injectable(InjectionType.Singleton)]
public class ConfigServer
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Servers;
namespace Core.Servers;
public class DatabaseServer
{
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Servers.Http;
namespace Core.Servers.Http;
public interface IHttpListener
{
+7 -7
View File
@@ -1,13 +1,13 @@
using System.Net.WebSockets;
using Core.Context;
using Core.Models.Config;
using Core.Servers.Http;
using Core.Services;
using Microsoft.Extensions.Primitives;
using Types.Annotations;
using Types.Context;
using Types.Models.Config;
using Types.Servers.Http;
using Types.Services;
using ILogger = Types.Models.Utils.ILogger;
using Core.Annotations;
using ILogger = Core.Models.Utils.ILogger;
namespace Types.Servers;
namespace Core.Servers;
[Injectable(InjectionType.Singleton)]
public class HttpServer
+2 -2
View File
@@ -1,6 +1,6 @@
using Types.Annotations;
using Core.Annotations;
namespace Types.Servers;
namespace Core.Servers;
[Injectable(InjectionType.Singleton)]
public class WebSocketServer
+1 -1
View File
@@ -1,4 +1,4 @@
namespace Types.Services;
namespace Core.Services;
public class I18nService
{
+4 -4
View File
@@ -1,8 +1,8 @@
using Types.Annotations;
using Types.Servers;
using ILogger = Types.Models.Utils.ILogger;
using Core.Annotations;
using Core.Servers;
using ILogger = Core.Models.Utils.ILogger;
namespace Types.Services;
namespace Core.Services;
[Injectable(InjectionType.Singleton)]
public class LocaleService
+5 -5
View File
@@ -1,9 +1,9 @@
using Types.Annotations;
using Types.Servers;
using Types.Utils;
using ILogger = Types.Models.Utils.ILogger;
using Core.Utils;
using Core.Annotations;
using Core.Servers;
using ILogger = Core.Models.Utils.ILogger;
namespace Types.Services;
namespace Core.Services;
[Injectable(InjectionType.Singleton)]
public class LocalisationService
+92
View File
@@ -0,0 +1,92 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using Core.Annotations;
namespace Core.Utils;
[Injectable(InjectionType.Singleton)]
public partial class HashUtil
{
/// <summary>
/// Create a 24 character id using the sha256 algorithm + current timestamp
/// </summary>
/// <returns>24 character hash</returns>
public static string Generate()
{
throw new NotImplementedException();
}
/// <summary>
/// is the passed in string a valid mongo id
/// </summary>
/// <param name="stringToCheck">String to check</param>
/// <returns>True when string is a valid mongo id</returns>
public static bool IsValidMongoId(string stringToCheck)
{
return MongoIdRegex().IsMatch(stringToCheck);
}
public static string GenerateMd5ForData(string data)
{
return GenerateHashForData(HashingAlgorithm.MD5, data);
}
public static string GenerateSha1ForData(string data)
{
return GenerateHashForData(HashingAlgorithm.SHA1, data);
}
public static string GenerateCrc32ForData(string data)
{
// TODO: Could not find a ms way of doing this.
// May need a custom impl to avoid an external lib. - CJ
throw new NotImplementedException();
}
/// <summary>
/// Create a hash for the data parameter
/// </summary>
/// <param name="algorithm">algorithm to use to hash</param>
/// <param name="data">data to be hashed</param>
/// <returns>hash value</returns>
/// <exception cref="NotImplementedException">thrown if the provided algorithm is not implemented</exception>>
public static string GenerateHashForData(HashingAlgorithm algorithm, string data)
{
switch (algorithm)
{
case HashingAlgorithm.MD5:
var md5HashData = MD5.HashData(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(md5HashData).Replace("-", string.Empty);
case HashingAlgorithm.SHA1:
var sha1HashData = SHA1.HashData(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(sha1HashData).Replace("-", string.Empty);
}
throw new NotImplementedException("Provided hash algorithm is not supported.");
}
/// <summary>
/// Generates an account ID for a profile
/// </summary>
/// <returns>Generated account ID</returns>
public static int GenerateAccountId()
{
const int min = 1000000;
const int max = 1999999;
var random = new Random();
return random.Next() * (max - min + 1) + min;
}
[GeneratedRegex("^[a-fA-F0-9]{24}$", RegexOptions.IgnoreCase, "en")]
private static partial Regex MongoIdRegex();
}
public enum HashingAlgorithm
{
MD5,
SHA1,
}
+4 -4
View File
@@ -1,8 +1,8 @@
using Types.Annotations;
using Types.Models.Logging;
using ILogger = Types.Models.Utils.ILogger;
using Core.Models.Logging;
using Core.Annotations;
using ILogger = Core.Models.Utils.ILogger;
namespace Types.Utils.Logging;
namespace Core.Utils.Logging;
[Injectable(InjectionType.Singleton)]
public class SimpleTextLogger : ILogger
+168 -1
View File
@@ -1,6 +1,173 @@
namespace Types.Utils;
using System.Security.Cryptography;
using Core.Annotations;
namespace Core.Utils;
// TODO: Finish porting this class
[Injectable(InjectionType.Singleton)]
public class RandomUtil
{
private readonly Random _random = new();
/// <summary>
/// Generates a random integer between the specified minimum and maximum values, inclusive.
/// </summary>
/// <param name="min">The minimum value (inclusive).</param>
/// <param name="max">The maximum value (inclusive).</param>
/// <returns>A random integer between the specified minimum and maximum values.</returns>
public int GetInt(int min, int max)
{
// Prevents a potential integer overflow.
if (max == int.MaxValue)
{
max -= 1;
}
// maxVal is exclusive of the passed value, so add 1
return max > min ? _random.Next(min, max + 1) : min;
}
/// <summary>
/// Generates a random integer between 1 (inclusive) and the specified maximum value (exclusive).
/// If the maximum value is less than or equal to 1, it returns 1.
/// </summary>
/// <param name="max">The upper bound (exclusive) for the random integer generation.</param>
/// <returns>A random integer between 1 and max - 1, or 1 if max is less than or equal to 1.</returns>
public int GetIntEx(int max)
{
return max > 2 ? _random.Next(1, max - 1) : 1;
}
/// <summary>
/// Generates a random floating-point number within the specified range.
/// </summary>
/// <param name="min">The minimum value of the range (inclusive).</param>
/// <param name="max">The maximum value of the range (exclusive).</param>
/// <returns>A random floating-point number between `min` (inclusive) and `max` (exclusive).</returns>
public float GetFloat(float min, float max)
{
return (float)GetSecureRandomNumber() * (max - min) + min;
}
/// <summary>
/// Generates a random boolean value.
/// </summary>
/// <returns>A random boolean value, where the probability of `true` and `false` is approximately equal.</returns>
public bool GetBool()
{
return GetSecureRandomNumber() < 0.5;
}
/// <summary>
/// Calculates the percentage of a given number and returns the result.
/// </summary>
/// <param name="percent">The percentage to calculate.</param>
/// <param name="number">The number to calculate the percentage of.</param>
/// <param name="toFixed">The number of decimal places to round the result to (default is 2).</param>
/// <returns>The calculated percentage of the given number, rounded to the specified number of decimal places.</returns>
public float GetPercentOfValue(float percent, float number, int toFixed = 2)
{
var num = percent * number / 100;
return (float)Math.Round(num, toFixed);
}
/// <summary>
/// Reduces a given number by a specified percentage.
/// </summary>
/// <param name="number">The original number to be reduced.</param>
/// <param name="percentage">The percentage by which to reduce the number.</param>
/// <returns>The reduced number after applying the percentage reduction.</returns>
public float ReduceValueByPercent(float number, float percentage)
{
var reductionAmount = number * percentage / 100;
return number - reductionAmount;
}
/// <summary>
/// Determines if a random event occurs based on the given chance percentage.
/// </summary>
/// <param name="chancePercent">The percentage chance (0-100) that the event will occur.</param>
/// <returns>`true` if the event occurs, `false` otherwise.</returns>
public bool GetChance100(float chancePercent)
{
chancePercent = Math.Clamp(chancePercent, 0f, 100f);
return GetIntEx(100) <= chancePercent;
}
/// <summary>
/// Returns a random string from the provided collection of strings.
///
/// This method is separate from GetCollectionValue so we can use a generic inference with GetCollectionValue.
/// </summary>
/// <param name="collection">The collection of strings to select a random value from.</param>
/// <returns>A randomly selected string from the array.</returns>
public string GetStringCollectionValue(IEnumerable<string> collection)
{
return collection.ElementAt(GetInt(0, collection.Count() - 1));
}
/// <summary>
/// Returns a random string from the provided collection of strings.
/// </summary>
/// <param name="collection"></param>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
/// <returns>A random element from the collection.</returns>
/// <remarks>This was formerly getArrayValue() in the node server</remarks>
public T GetCollectionValue<T>(IEnumerable<T> collection)
{
return collection.ElementAt(GetInt(0, collection.Count() - 1));
}
/// <summary>
/// Gets a random key from the given dictionary
/// </summary>
/// <param name="dictionary">The dictionary from which to retrieve a key.</param>
/// <typeparam name="TKey">Type of key</typeparam>
/// <typeparam name="TVal">Type of Value</typeparam>
/// <returns>A random TKey representing one of the keys of the dictionary.</returns>
public TKey GetKey<TKey, TVal>(Dictionary<TKey, TVal> dictionary) where TKey : notnull
{
return GetCollectionValue(dictionary.Keys);
}
/// <summary>
/// Generates a secure random number between 0 (inclusive) and 1 (exclusive).
///
/// This method uses the `crypto` module to generate a 48-bit random integer,
/// which is then divided by the maximum possible 48-bit integer value to
/// produce a floating-point number in the range [0, 1).
/// </summary>
/// <returns>A secure random number between 0 (inclusive) and 1 (exclusive).</returns>
private static double GetSecureRandomNumber()
{
var buffer = new byte[6];
using var rng = RandomNumberGenerator.Create();
// Fill buffer with random bytes
rng.GetBytes(buffer);
var integer = 0;
for (var i = 0; i < 6; i++)
{
integer = (integer << 8) | buffer[i];
}
const ulong maxInt = 1UL << 48;
return (double)integer / maxInt;
}
/// <summary>
/// Determines the number of decimal places in a number.
/// </summary>
/// <param name="num">The number to analyze.</param>
/// <returns>The number of decimal places, or 0 if none exist.</returns>
private static int GetNumberPrecision(double num)
{
return num.ToString().Split('.')[1]?.Length ?? 0;
}
}
+178
View File
@@ -0,0 +1,178 @@
using Core.Annotations;
namespace Core.Utils;
[Injectable(InjectionType.Singleton)]
public class TimeUtil
{
public const int OneHourAsSeconds = 3600;
/// <summary>
/// Formats the time part of a date as a UTC string.
/// </summary>
/// <param name="dateTime">The date to format in UTC.</param>
/// <returns>The formatted time as 'HH-MM-SS'.</returns>
public string FormatTime(DateTime dateTime)
{
var hour = Pad(dateTime.ToUniversalTime().Hour);
var minute = Pad(dateTime.ToUniversalTime().Minute);
var second = Pad(dateTime.ToUniversalTime().Second);
return $"{hour}-{minute}-{second}";
}
/// <summary>
/// Formats the date part of a date as a UTC string.
/// </summary>
/// <param name="dateTime">The date to format in UTC.</param>
/// <returns>The formatted date as 'YYYY-MM-DD'.</returns>
public string FormatDate(DateTime dateTime)
{
var day = Pad(dateTime.ToUniversalTime().Day);
var month = Pad(dateTime.ToUniversalTime().Month);
var year = Pad(dateTime.ToUniversalTime().Year);
return $"{year}-{month}-{day}";
}
/// <summary>
/// Gets the current date as a formatted UTC string.
/// </summary>
/// <returns>The current date as 'YYYY-MM-DD'.</returns>
public string GetDate()
{
return FormatDate(DateTime.Now);
}
/// <summary>
/// Gets the current time as a formatted UTC string.
/// </summary>
/// <returns>The current time as 'HH-MM-SS'.</returns>
public string GetTime()
{
return FormatTime(DateTime.Now);
}
/// <summary>
/// Gets the current timestamp in seconds in UTC.
/// </summary>
/// <returns>The current timestamp in seconds since the Unix epoch in UTC.</returns>
public long GetTimeStamp()
{
return DateTimeOffset.Now.ToUnixTimeSeconds();
}
/// <summary>
/// Gets the start of day timestamp for the given date
/// </summary>
/// <param name="dateTime">datetime to get the time stamp for, if null it uses current date.</param>
/// <returns>Unix epoch for the start of day of the calculated date</returns>
public long GetStartOfDayTimeStamp(DateTime? dateTime)
{
var now = dateTime ?? DateTime.Now;
return new DateTimeOffset(new DateTime(now.Year, now.Month, now.Day, 0, 0, 0))
.ToUnixTimeSeconds();
}
/// <summary>
/// Get timestamp of today + passed in day count
/// </summary>
/// <param name="daysFromNow">Days from now</param>
/// <returns></returns>
public long GetTimeStampFromNowDays(int daysFromNow)
{
return DateTimeOffset.Now.AddDays(daysFromNow).ToUnixTimeSeconds();
}
/// <summary>
/// Get timestamp of today + passed in hour count
/// </summary>
/// <param name="hoursFromNow"></param>
/// <returns></returns>
public long GetTimeStampFromNowHours(int hoursFromNow)
{
return DateTimeOffset.Now.AddHours(hoursFromNow).ToUnixTimeSeconds();
}
/// <summary>
/// Gets the current time in UTC in a format suitable for mail in EFT.
/// </summary>
/// <returns>The current time as 'HH:MM' in UTC.</returns>
public string GetTimeMailFormat()
{
return DateTime.UtcNow.ToString("HH:mm");
}
/// <summary>
/// Gets the current date in UTC in a format suitable for emails in EFT.
/// </summary>
/// <returns>The current date as 'DD.MM.YYYY' in UTC.</returns>
public string GetDateMailFormat()
{
return DateTime.UtcNow.ToString("dd.MM.yyyy");
}
/// <summary>
/// Converts a number of hours into seconds.
/// </summary>
/// <param name="hours">The number of hours to convert.</param>
/// <returns>The equivalent number of seconds.</returns>
public int GetHoursAsSeconds(int hours)
{
return OneHourAsSeconds * hours;
}
/// <summary>
/// Gets the time stamp of the start of the next hour in UTC
/// </summary>
/// <returns>Time stamp of the next hour in unix time seconds</returns>
public long GetTimeStampOfNextHour()
{
var now = DateTime.UtcNow;
var nextHour = new DateTime(
now.Year,
now.Month,
now.Day,
now.Hour,
0,
0,
DateTimeKind.Utc
).AddHours(1);
return new DateTimeOffset(nextHour).ToUnixTimeSeconds();
}
/// <summary>
/// Returns the current days timestamp at 00:00
/// e.g. current time: 13th March 14:22 will return 13th March 00:00
/// </summary>
/// <returns>Timestamp</returns>
public long GetTodayMidNightTimeStamp()
{
var now = DateTime.UtcNow;
var midNight = new DateTime(
now.Year,
now.Month,
now.Day,
0,
0,
0,
DateTimeKind.Utc
);
return new DateTimeOffset(midNight).ToUnixTimeSeconds();
}
/// <summary>
/// Pads a number with a leading zero if it is less than 10.
/// </summary>
/// <param name="number">The number to pad.</param>
/// <returns>The padded number as a string.</returns>
private static string Pad(int number)
{
return number.ToString().PadLeft(2, '0');
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
using Types.Annotations;
using Types.Servers;
using Core.Annotations;
using Core.Servers;
namespace Server;