Files
git.stella-ops.org/src/Signals/StellaOps.Signals/Storage/FileSystemCallgraphArtifactStore.cs
2025-10-28 15:10:40 +02:00

61 lines
2.5 KiB
C#

using System;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Signals.Options;
using StellaOps.Signals.Storage.Models;
namespace StellaOps.Signals.Storage;
/// <summary>
/// Stores callgraph artifacts on the local filesystem.
/// </summary>
internal sealed class FileSystemCallgraphArtifactStore : ICallgraphArtifactStore
{
private readonly SignalsArtifactStorageOptions storageOptions;
private readonly ILogger<FileSystemCallgraphArtifactStore> logger;
public FileSystemCallgraphArtifactStore(IOptions<SignalsOptions> options, ILogger<FileSystemCallgraphArtifactStore> logger)
{
ArgumentNullException.ThrowIfNull(options);
storageOptions = options.Value.Storage;
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<StoredCallgraphArtifact> SaveAsync(CallgraphArtifactSaveRequest request, Stream content, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(content);
var root = storageOptions.RootPath;
var directory = Path.Combine(root, Sanitize(request.Language), Sanitize(request.Component), Sanitize(request.Version));
Directory.CreateDirectory(directory);
var fileName = string.IsNullOrWhiteSpace(request.FileName)
? FormattableString.Invariant($"artifact-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}.bin")
: request.FileName.Trim();
var destinationPath = Path.Combine(directory, fileName);
await using (var fileStream = File.Create(destinationPath))
{
await content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
var fileInfo = new FileInfo(destinationPath);
logger.LogInformation("Stored callgraph artifact at {Path} (length={Length}).", destinationPath, fileInfo.Length);
return new StoredCallgraphArtifact(
Path.GetRelativePath(root, destinationPath),
fileInfo.Length,
request.Hash,
request.ContentType);
}
private static string Sanitize(string value)
=> string.Join('_', value.Split(Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).ToLowerInvariant();
}