Files
2026-05-31 11:06:50 +02:00

193 lines
8.7 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
/// Represents a file metadata cached for synchronization or tracking purposes. Provides properties for file path, size, timestamps, hash, and synchronization state.
/// </summary>
/// <param name="relativePath">The relative path of the file within the root directory. Used to identify and locate the file in the cache.</param>
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;
/// <summary>
/// Gets the current time as the number of seconds that have elapsed since the Unix epoch (January 1, 1970, 00:00:00 UTC).
/// </summary>
private static long Now => DateTimeOffset.UtcNow.ToUnixTimeSeconds();
/// <summary>
/// Computes the SHA-1 hash of the specified byte span and returns its hexadecimal string representation.
/// </summary>
/// <param name="data">The input data to hash as a read-only span of bytes.</param>
/// <returns>A lowercase hexadecimal string representing the SHA-1 hash of the input data.</returns>
private static string Sha1FromSpan(ReadOnlySpan<byte> data)
{
Span<byte> hash = stackalloc byte[20]; // SHA1 = 20 bytes
SHA1.HashData(data, hash);
return Convert.ToHexString(hash).ToLowerInvariant();
}
/// <summary>
/// Computes the relative path from the specified root directory to the given file path, using forward slashes as directory separators.
/// </summary>
/// <param name="filePath">The absolute path to the target file. Cannot be null.</param>
/// <param name="rootPath">The absolute path to the root directory from which to calculate the relative path. Cannot be null.</param>
/// <returns>A relative path from the root directory to the file, using forward slashes ('/') as directory separators.</returns>
private static string GetRelativePath(string filePath, string rootPath)
=> Path.GetRelativePath(rootPath, filePath).Replace(Path.DirectorySeparatorChar, '/');
/// <summary>
/// Initializes a new instance of the <see cref="CachedFileMetadata"/> class using the specified file path and root directory. Loads the file's data and metadata into the cache.
/// </summary>
/// <param name="filePath">The full path to the file to be cached. Must refer to an existing file.</param>
/// <param name="rootPath">The root directory path used to compute the relative path for the cached file.</param>
/// <exception cref="FileNotFoundException">Thrown if the file specified by filePath does not exist.</exception>
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);
}
/// <summary>
/// Initializes a new instance of the <see cref="CachedFileMetadata"/> class using data from the provided keyvalue group.
/// </summary>
/// <param name="group">A keyvalue group containing the file data used to initialize the object's properties.</param>
public CachedFileMetadata(KeyValueGroup group) : this(group.Key)
{
foreach (var node in group.Nodes.Cast<KeyValue.Models.KeyValuePair?>())
{
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;
}
}
}
/// <summary>
/// Sets the local time property to the current system time.
/// </summary>
public void SetLocalTimeToNow() => LocalTime = Now;
/// <summary>
/// Sets the current time to the system's current date and time.
/// </summary>
public void SetTimeToNow() => Time = Now;
/// <summary>
/// Sets both the local time and the time properties to the current value of the system clock.
/// </summary>
public void SetLocalTimeAndTimeToNow()
{
var epoch = Now;
LocalTime = epoch;
Time = epoch;
}
/// <summary>
/// Sets the local time using the specified timestamp.
/// </summary>
/// <param name="timestamp">The point in time, expressed as a DateTimeOffset, to set as the local time. The value is converted to Unix time in seconds.</param>
public void SetLocalTime(DateTimeOffset timestamp)
=> LocalTime = timestamp.ToUnixTimeSeconds();
/// <summary>
/// Sets the current time value using the specified timestamp.
/// </summary>
/// <param name="timestamp">The point in time to set, represented as a DateTimeOffset. The value is converted to Unix time in seconds.</param>
public void SetTime(DateTimeOffset timestamp)
=> Time = timestamp.ToUnixTimeSeconds();
/// <summary>
/// Sets the local time and time properties using the specified timestamp.
/// </summary>
/// <param name="timestamp">The date and time value to use, including the offset from Coordinated Universal Time (UTC).</param>
public void SetLocalTimeAndTime(DateTimeOffset timestamp)
{
LocalTime = timestamp.ToUnixTimeSeconds();
Time = timestamp.ToUnixTimeSeconds();
}
/// <summary>
/// Gets the local date and time represented by the current Unix timestamp value.
/// </summary>
/// <returns>A <see cref="DateTimeOffset"/> that represents the local date and time corresponding to the stored Unix time in seconds.</returns>
public DateTimeOffset GetLocalDateTime()
=> DateTimeOffset.FromUnixTimeSeconds(LocalTime);
/// <summary>
/// Gets the date and time represented by the current Unix timestamp value.
/// </summary>
/// <returns>A DateTimeOffset value corresponding to the Unix timestamp stored in the current instance.</returns>
public DateTimeOffset GetDateTime()
=> DateTimeOffset.FromUnixTimeSeconds(Time);
/// <summary>
/// Gets the current remote date and time as a DateTimeOffset value.
/// </summary>
/// <returns>A DateTimeOffset representing the remote time, converted from the stored Unix timestamp.</returns>
public DateTimeOffset GetRemoteDateTime()
=> DateTimeOffset.FromUnixTimeSeconds(RemoteTime);
/// <summary>
/// Exports the current object as a keyvalue group containing essential information about its state and properties.
/// </summary>
/// <returns> A <see cref="KeyValueGroup"/> instance populated with core data such as the relative path, size, timestamps, SHA checksum, and the current synchronization and persistence states.</returns>
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());
}