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());
}