89 lines
6.0 KiB
C#
89 lines
6.0 KiB
C#
using System.Security.Cryptography;
|
|
|
|
namespace Launcher.Next.Application;
|
|
|
|
public sealed record LegacySettingsSnapshot(string SourceEndpoint, string SourceFingerprint, string Sha256, long Length, DateTimeOffset LastWriteUtc, DateTimeOffset AcquiredUtc, string LocalSnapshotPath, bool OfflineFallback);
|
|
|
|
public static class LegacyNetworkConfiguration
|
|
{
|
|
public const string ApprovedSettingsEndpoint = @"\\phimproject.com\common\BIM\save\All_Serv_Install\Settings.xml";
|
|
public const string LegacyBimAlias = @"N:\BIM";
|
|
public const string ApprovedBimEndpoint = @"\\phimproject.com\common\BIM";
|
|
|
|
public static string ResolveApprovedAlias(string legacyPath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(legacyPath)) throw new InvalidOperationException("LEGACY_NETWORK_PATH_INVALID");
|
|
var normalized = legacyPath.Replace('/', '\\');
|
|
if (string.Equals(normalized, LegacyBimAlias, StringComparison.OrdinalIgnoreCase)) return ApprovedBimEndpoint;
|
|
var prefix = LegacyBimAlias + "\\";
|
|
if (!normalized.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("LEGACY_NETWORK_ALIAS_NOT_APPROVED");
|
|
return ApprovedBimEndpoint + normalized[(prefix.Length - 1)..];
|
|
}
|
|
|
|
public static LegacySettingsSnapshot AcquireApprovedSnapshot(string endpoint, string localRoot)
|
|
{
|
|
if (!string.Equals(endpoint, ApprovedSettingsEndpoint, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("LEGACY_SETTINGS_ENDPOINT_NOT_APPROVED");
|
|
if (!Path.IsPathFullyQualified(endpoint) || !endpoint.StartsWith(@"\\", StringComparison.Ordinal) || endpoint.Contains('?') || endpoint.Contains('*')) throw new InvalidOperationException("LEGACY_SETTINGS_ENDPOINT_INVALID");
|
|
if (!File.Exists(endpoint) || File.GetAttributes(endpoint).HasFlag(FileAttributes.ReparsePoint)) return OfflineSnapshot(endpoint, localRoot);
|
|
var first = new FileInfo(endpoint);
|
|
var sourceHashBefore = Hash(endpoint);
|
|
var snapshots = Path.Combine(Path.GetFullPath(localRoot), "configuration-snapshots");
|
|
Directory.CreateDirectory(snapshots);
|
|
var temporary = Path.Combine(snapshots, Guid.NewGuid().ToString("N") + ".tmp");
|
|
File.Copy(endpoint, temporary, false);
|
|
var copiedHash = Hash(temporary);
|
|
var after = new FileInfo(endpoint);
|
|
var sourceHashAfter = Hash(endpoint);
|
|
if (first.Length != after.Length || first.LastWriteTimeUtc != after.LastWriteTimeUtc || !string.Equals(sourceHashBefore, sourceHashAfter, StringComparison.Ordinal) || !string.Equals(copiedHash, sourceHashAfter, StringComparison.Ordinal))
|
|
{
|
|
File.Delete(temporary);
|
|
throw new InvalidOperationException("LEGACY_SETTINGS_CHANGED_DURING_ACQUISITION");
|
|
}
|
|
var final = Path.Combine(snapshots, copiedHash + ".xml");
|
|
if (File.Exists(final)) File.Delete(temporary); else File.Move(temporary, final);
|
|
if (!string.Equals(Hash(final), copiedHash, StringComparison.Ordinal)) throw new InvalidOperationException("LEGACY_SETTINGS_SNAPSHOT_HASH_MISMATCH");
|
|
return new(endpoint, Fingerprint(endpoint, copiedHash), copiedHash, first.Length, new DateTimeOffset(first.LastWriteTimeUtc), DateTimeOffset.UtcNow, final, false);
|
|
}
|
|
|
|
private static LegacySettingsSnapshot OfflineSnapshot(string endpoint, string localRoot)
|
|
{
|
|
var snapshots = Path.Combine(Path.GetFullPath(localRoot), "configuration-snapshots");
|
|
if (!Directory.Exists(snapshots)) throw new InvalidOperationException("LEGACY_SETTINGS_NETWORK_UNAVAILABLE");
|
|
var candidate = Directory.GetFiles(snapshots, "*.xml").OrderByDescending(File.GetLastWriteTimeUtc).FirstOrDefault();
|
|
if (candidate is null) throw new InvalidOperationException("LEGACY_SETTINGS_NETWORK_UNAVAILABLE");
|
|
var info = new FileInfo(candidate);
|
|
var hash = Hash(candidate);
|
|
return new(endpoint, Fingerprint(endpoint, hash), hash, info.Length, new DateTimeOffset(info.LastWriteTimeUtc), DateTimeOffset.UtcNow, candidate, true);
|
|
}
|
|
|
|
private static string Hash(string path) { using var stream = File.OpenRead(path); return Convert.ToHexString(SHA256.HashData(stream)); }
|
|
private static string Fingerprint(string endpoint, string hash) => Convert.ToHexString(SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(endpoint + "|" + hash)));
|
|
}
|
|
|
|
public static class LegacyReadOnlySnapshots
|
|
{
|
|
public static LegacySettingsSnapshot Acquire(string source, string localRoot, string role)
|
|
{
|
|
if (!Path.IsPathFullyQualified(source) || !source.StartsWith(@"\\", StringComparison.Ordinal) || source.Contains('?') || source.Contains('*') || !File.Exists(source) || File.GetAttributes(source).HasFlag(FileAttributes.ReparsePoint)) throw new InvalidOperationException("READONLY_SNAPSHOT_SOURCE_INVALID");
|
|
var before = new FileInfo(source);
|
|
var beforeHash = Hash(source);
|
|
var directory = Path.Combine(Path.GetFullPath(localRoot), "configuration-snapshots", role);
|
|
Directory.CreateDirectory(directory);
|
|
var temporary = Path.Combine(directory, Guid.NewGuid().ToString("N") + ".tmp");
|
|
File.Copy(source, temporary, false);
|
|
var copiedHash = Hash(temporary);
|
|
var after = new FileInfo(source);
|
|
var afterHash = Hash(source);
|
|
if (before.Length != after.Length || before.LastWriteTimeUtc != after.LastWriteTimeUtc || beforeHash != afterHash || copiedHash != afterHash)
|
|
{
|
|
File.Delete(temporary);
|
|
throw new InvalidOperationException("READONLY_SNAPSHOT_SOURCE_CHANGED");
|
|
}
|
|
var final = Path.Combine(directory, copiedHash + ".xml");
|
|
if (File.Exists(final)) File.Delete(temporary); else File.Move(temporary, final);
|
|
return new(source, Convert.ToHexString(SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(role + "|" + source + "|" + copiedHash))), copiedHash, before.Length, new DateTimeOffset(before.LastWriteTimeUtc), DateTimeOffset.UtcNow, final, false);
|
|
}
|
|
|
|
private static string Hash(string path) { using var stream = File.OpenRead(path); return Convert.ToHexString(SHA256.HashData(stream)); }
|
|
}
|