Forge
csharpdeeb25a2
1using System.Text.Json;
2using System.Text.Json.Serialization;
3
4namespace CascadeIDE.Features.Intercom.Transport;
5
6/// <summary>Offline outbox: <c>.cascade-ide/intercom-transport-outbox.ndjson</c> (ADR 0144 §9).</summary>
7public sealed class IntercomTransportOutboundQueue
8{
9 private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);
10
11 private string? _workspaceRoot;
12
13 public void SetWorkspaceRoot(string? workspaceRoot) => _workspaceRoot = workspaceRoot;
14
15 public async Task EnqueueAsync(IntercomOutboundQueueEntry entry, CancellationToken ct = default)
16 {
17 var path = OutboxPath();
18 if (path is null)
19 return;
20
21 Directory.CreateDirectory(Path.GetDirectoryName(path)!);
22 var line = JsonSerializer.Serialize(entry, Json);
23 await File.AppendAllTextAsync(path, line + Environment.NewLine, ct).ConfigureAwait(false);
24 }
25
26 public async Task<IReadOnlyList<IntercomOutboundQueueEntry>> ReadAllAsync(CancellationToken ct = default)
27 {
28 var path = OutboxPath();
29 if (path is null || !File.Exists(path))
30 return [];
31
32 var list = new List<IntercomOutboundQueueEntry>();
33 await using var stream = File.OpenRead(path);
34 using var reader = new StreamReader(stream);
35 while (true)
36 {
37 ct.ThrowIfCancellationRequested();
38 var line = await reader.ReadLineAsync(ct).ConfigureAwait(false);
39 if (line is null)
40 break;
41 if (string.IsNullOrWhiteSpace(line))
42 continue;
43 try
44 {
45 var entry = JsonSerializer.Deserialize<IntercomOutboundQueueEntry>(line, Json);
46 if (entry is not null)
47 list.Add(entry);
48 }
49 catch (JsonException)
50 {
51 // skip corrupt line
52 }
53 }
54
55 return list;
56 }
57
58 public async Task ReplaceAllAsync(IReadOnlyList<IntercomOutboundQueueEntry> remaining, CancellationToken ct = default)
59 {
60 var path = OutboxPath();
61 if (path is null)
62 return;
63
64 if (remaining.Count == 0)
65 {
66 if (File.Exists(path))
67 File.Delete(path);
68 return;
69 }
70
71 Directory.CreateDirectory(Path.GetDirectoryName(path)!);
72 var lines = remaining.Select(e => JsonSerializer.Serialize(e, Json));
73 await File.WriteAllTextAsync(path, string.Join(Environment.NewLine, lines) + Environment.NewLine, ct)
74 .ConfigureAwait(false);
75 }
76
77 private string? OutboxPath()
78 {
79 if (string.IsNullOrWhiteSpace(_workspaceRoot))
80 return null;
81 return Path.Combine(_workspaceRoot, ".cascade-ide", "intercom-transport-outbox.ndjson");
82 }
83}
84
85public sealed record IntercomOutboundQueueEntry(
86 [property: JsonPropertyName("topic_id")] string TopicId,
87 [property: JsonPropertyName("request")] IntercomAppendEventRequestDto Request,
88 [property: JsonPropertyName("attempts")] int Attempts = 0);
89
View only · write via MCP/CIDE