178 lines
6.1 KiB
C#
178 lines
6.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using MongoDB.Bson;
|
|
|
|
namespace StellaOps.Concelier.Connector.Distro.Debian.Internal;
|
|
|
|
internal sealed record DebianCursor(
|
|
DateTimeOffset? LastPublished,
|
|
IReadOnlyCollection<string> ProcessedAdvisoryIds,
|
|
IReadOnlyCollection<Guid> PendingDocuments,
|
|
IReadOnlyCollection<Guid> PendingMappings,
|
|
IReadOnlyDictionary<string, DebianFetchCacheEntry> FetchCache)
|
|
{
|
|
private static readonly IReadOnlyCollection<string> EmptyIds = Array.Empty<string>();
|
|
private static readonly IReadOnlyCollection<Guid> EmptyGuidList = Array.Empty<Guid>();
|
|
private static readonly IReadOnlyDictionary<string, DebianFetchCacheEntry> EmptyCache =
|
|
new Dictionary<string, DebianFetchCacheEntry>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public static DebianCursor Empty { get; } = new(null, EmptyIds, EmptyGuidList, EmptyGuidList, EmptyCache);
|
|
|
|
public static DebianCursor FromBson(BsonDocument? document)
|
|
{
|
|
if (document is null || document.ElementCount == 0)
|
|
{
|
|
return Empty;
|
|
}
|
|
|
|
DateTimeOffset? lastPublished = null;
|
|
if (document.TryGetValue("lastPublished", out var lastValue))
|
|
{
|
|
lastPublished = lastValue.BsonType switch
|
|
{
|
|
BsonType.String when DateTimeOffset.TryParse(lastValue.AsString, out var parsed) => parsed.ToUniversalTime(),
|
|
BsonType.DateTime => DateTime.SpecifyKind(lastValue.ToUniversalTime(), DateTimeKind.Utc),
|
|
_ => null,
|
|
};
|
|
}
|
|
|
|
var processed = ReadStringArray(document, "processedIds");
|
|
var pendingDocuments = ReadGuidArray(document, "pendingDocuments");
|
|
var pendingMappings = ReadGuidArray(document, "pendingMappings");
|
|
var cache = ReadCache(document);
|
|
|
|
return new DebianCursor(lastPublished, processed, pendingDocuments, pendingMappings, cache);
|
|
}
|
|
|
|
public BsonDocument ToBsonDocument()
|
|
{
|
|
var document = new BsonDocument
|
|
{
|
|
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(static id => id.ToString())),
|
|
["pendingMappings"] = new BsonArray(PendingMappings.Select(static id => id.ToString())),
|
|
};
|
|
|
|
if (LastPublished.HasValue)
|
|
{
|
|
document["lastPublished"] = LastPublished.Value.UtcDateTime;
|
|
}
|
|
|
|
if (ProcessedAdvisoryIds.Count > 0)
|
|
{
|
|
document["processedIds"] = new BsonArray(ProcessedAdvisoryIds);
|
|
}
|
|
|
|
if (FetchCache.Count > 0)
|
|
{
|
|
var cacheDoc = new BsonDocument();
|
|
foreach (var (key, entry) in FetchCache)
|
|
{
|
|
cacheDoc[key] = entry.ToBsonDocument();
|
|
}
|
|
|
|
document["fetchCache"] = cacheDoc;
|
|
}
|
|
|
|
return document;
|
|
}
|
|
|
|
public DebianCursor WithPendingDocuments(IEnumerable<Guid> ids)
|
|
=> this with { PendingDocuments = ids?.Distinct().ToArray() ?? EmptyGuidList };
|
|
|
|
public DebianCursor WithPendingMappings(IEnumerable<Guid> ids)
|
|
=> this with { PendingMappings = ids?.Distinct().ToArray() ?? EmptyGuidList };
|
|
|
|
public DebianCursor WithProcessed(DateTimeOffset published, IEnumerable<string> ids)
|
|
=> this with
|
|
{
|
|
LastPublished = published.ToUniversalTime(),
|
|
ProcessedAdvisoryIds = ids?.Where(static id => !string.IsNullOrWhiteSpace(id))
|
|
.Select(static id => id.Trim())
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToArray() ?? EmptyIds
|
|
};
|
|
|
|
public DebianCursor WithFetchCache(IDictionary<string, DebianFetchCacheEntry>? cache)
|
|
{
|
|
if (cache is null || cache.Count == 0)
|
|
{
|
|
return this with { FetchCache = EmptyCache };
|
|
}
|
|
|
|
return this with { FetchCache = new Dictionary<string, DebianFetchCacheEntry>(cache, StringComparer.OrdinalIgnoreCase) };
|
|
}
|
|
|
|
public bool TryGetCache(string key, out DebianFetchCacheEntry entry)
|
|
{
|
|
if (FetchCache.Count == 0)
|
|
{
|
|
entry = DebianFetchCacheEntry.Empty;
|
|
return false;
|
|
}
|
|
|
|
return FetchCache.TryGetValue(key, out entry!);
|
|
}
|
|
|
|
private static IReadOnlyCollection<string> ReadStringArray(BsonDocument document, string field)
|
|
{
|
|
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
|
|
{
|
|
return EmptyIds;
|
|
}
|
|
|
|
var list = new List<string>(array.Count);
|
|
foreach (var element in array)
|
|
{
|
|
if (element.BsonType == BsonType.String)
|
|
{
|
|
var str = element.AsString.Trim();
|
|
if (!string.IsNullOrEmpty(str))
|
|
{
|
|
list.Add(str);
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
|
|
{
|
|
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
|
|
{
|
|
return EmptyGuidList;
|
|
}
|
|
|
|
var list = new List<Guid>(array.Count);
|
|
foreach (var element in array)
|
|
{
|
|
if (Guid.TryParse(element.ToString(), out var guid))
|
|
{
|
|
list.Add(guid);
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
private static IReadOnlyDictionary<string, DebianFetchCacheEntry> ReadCache(BsonDocument document)
|
|
{
|
|
if (!document.TryGetValue("fetchCache", out var value) || value is not BsonDocument cacheDocument || cacheDocument.ElementCount == 0)
|
|
{
|
|
return EmptyCache;
|
|
}
|
|
|
|
var cache = new Dictionary<string, DebianFetchCacheEntry>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var element in cacheDocument.Elements)
|
|
{
|
|
if (element.Value is BsonDocument entry)
|
|
{
|
|
cache[element.Name] = DebianFetchCacheEntry.FromBson(entry);
|
|
}
|
|
}
|
|
|
|
return cache;
|
|
}
|
|
}
|