using System.Security.Cryptography; using System.IO; using EonaCat.FirstLight.SaveTransfer.VdfGenerator.KeyValue.Models; using EonaCat.FirstLight.SaveTransfer.VdfGenerator; namespace EonaCat.FirstLight.SaveTransfer.VdfGenerator.Models; /// /// Represents a file metadata cached for synchronization or tracking purposes. Provides properties for file path, size, timestamps, hash, and synchronization state. /// /// The relative path of the file within the root directory. Used to identify and locate the file in the cache. public class CachedFileMetadata(string relativePath) { private const string DefaultSha = "0000000000000000000000000000000000000000"; public string RelativePath { get; set; } = relativePath; public int Root { get; set; } public int Size { get; set; } public long LocalTime { get; set; } public long Time { get; set; } public long RemoteTime { get; set; } public string Sha { get; set; } = DefaultSha; public int SyncState { get; set; } public int PersistState { get; set; } public int PlatformsToSync2 { get; set; } = -1; /// /// Gets the current time as the number of seconds that have elapsed since the Unix epoch (January 1, 1970, 00:00:00 UTC). /// private static long Now => DateTimeOffset.UtcNow.ToUnixTimeSeconds(); /// /// Computes the SHA-1 hash of the specified byte span and returns its hexadecimal string representation. /// /// The input data to hash as a read-only span of bytes. /// A lowercase hexadecimal string representing the SHA-1 hash of the input data. private static string Sha1FromSpan(ReadOnlySpan data) { Span hash = stackalloc byte[20]; // SHA‑1 = 20 bytes SHA1.HashData(data, hash); return Convert.ToHexString(hash).ToLowerInvariant(); } /// /// Computes the relative path from the specified root directory to the given file path, using forward slashes as directory separators. /// /// The absolute path to the target file. Cannot be null. /// The absolute path to the root directory from which to calculate the relative path. Cannot be null. /// A relative path from the root directory to the file, using forward slashes ('/') as directory separators. private static string GetRelativePath(string filePath, string rootPath) => Path.GetRelativePath(rootPath, filePath).Replace(Path.DirectorySeparatorChar, '/'); /// /// Initializes a new instance of the class using the specified file path and root directory. Loads the file's data and metadata into the cache. /// /// The full path to the file to be cached. Must refer to an existing file. /// The root directory path used to compute the relative path for the cached file. /// Thrown if the file specified by filePath does not exist. public CachedFileMetadata(string filePath, string rootPath) : this(GetRelativePath(filePath, rootPath)) { if (!File.Exists(filePath)) throw new FileNotFoundException("File not found", filePath); var data = File.ReadAllBytes(filePath); Size = data.Length; SetLocalTimeAndTimeToNow(); Sha = Sha1FromSpan(data); } /// /// Initializes a new instance of the class using data from the provided key–value group. /// /// A key–value group containing the file data used to initialize the object's properties. public CachedFileMetadata(KeyValueGroup group) : this(group.Key) { foreach (var node in group.Nodes.Cast()) { switch (node?.Key) { case "root": Root = NumberParser.ParseInt(node.Value); break; case "size": Size = NumberParser.ParseInt(node.Value); break; case "localtime": LocalTime = NumberParser.ParseLong(node.Value); break; case "time": Time = NumberParser.ParseLong(node.Value); break; case "remotetime": RemoteTime = NumberParser.ParseLong(node.Value); break; case "sha": Sha = node.Value; break; case "syncstate": SyncState = NumberParser.ParseInt(node.Value); break; case "persiststate": PersistState = NumberParser.ParseInt(node.Value); break; case "platformstosync2": PlatformsToSync2 = NumberParser.ParseInt(node.Value); break; } } } /// /// Sets the local time property to the current system time. /// public void SetLocalTimeToNow() => LocalTime = Now; /// /// Sets the current time to the system's current date and time. /// public void SetTimeToNow() => Time = Now; /// /// Sets both the local time and the time properties to the current value of the system clock. /// public void SetLocalTimeAndTimeToNow() { var epoch = Now; LocalTime = epoch; Time = epoch; } /// /// Sets the local time using the specified timestamp. /// /// The point in time, expressed as a DateTimeOffset, to set as the local time. The value is converted to Unix time in seconds. public void SetLocalTime(DateTimeOffset timestamp) => LocalTime = timestamp.ToUnixTimeSeconds(); /// /// Sets the current time value using the specified timestamp. /// /// The point in time to set, represented as a DateTimeOffset. The value is converted to Unix time in seconds. public void SetTime(DateTimeOffset timestamp) => Time = timestamp.ToUnixTimeSeconds(); /// /// Sets the local time and time properties using the specified timestamp. /// /// The date and time value to use, including the offset from Coordinated Universal Time (UTC). public void SetLocalTimeAndTime(DateTimeOffset timestamp) { LocalTime = timestamp.ToUnixTimeSeconds(); Time = timestamp.ToUnixTimeSeconds(); } /// /// Gets the local date and time represented by the current Unix timestamp value. /// /// A that represents the local date and time corresponding to the stored Unix time in seconds. public DateTimeOffset GetLocalDateTime() => DateTimeOffset.FromUnixTimeSeconds(LocalTime); /// /// Gets the date and time represented by the current Unix timestamp value. /// /// A DateTimeOffset value corresponding to the Unix timestamp stored in the current instance. public DateTimeOffset GetDateTime() => DateTimeOffset.FromUnixTimeSeconds(Time); /// /// Gets the current remote date and time as a DateTimeOffset value. /// /// A DateTimeOffset representing the remote time, converted from the stored Unix timestamp. public DateTimeOffset GetRemoteDateTime() => DateTimeOffset.FromUnixTimeSeconds(RemoteTime); /// /// Exports the current object as a key–value group containing essential information about its state and properties. /// /// A instance populated with core data such as the relative path, size, timestamps, SHA checksum, and the current synchronization and persistence states. public KeyValueGroup ExportAsKvGroup() => new KeyValueGroup(RelativePath) .Add("root", Root.ToString()) .Add("size", Size.ToString()) .Add("localtime", LocalTime.ToString()) .Add("time", Time.ToString()) .Add("remotetime", RemoteTime.ToString()) .Add("sha", Sha) .Add("syncstate", SyncState.ToString()) .Add("persiststate", PersistState.ToString()) .Add("platformstosync2", PlatformsToSync2.ToString()); }