Skip to content

Commit 01e7e6d

Browse files
committed
PR comments
1 parent 81f8c02 commit 01e7e6d

File tree

4 files changed

+165
-217
lines changed

4 files changed

+165
-217
lines changed

App/Models/SyncSessionModel.cs

+60-163
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Collections.ObjectModel;
43
using System.Linq;
54
using Coder.Desktop.App.Converters;
65
using Coder.Desktop.MutagenSdk.Proto.Synchronization;
@@ -49,163 +48,9 @@ public string Description(string linePrefix = "")
4948
}
5049
}
5150

52-
public enum SyncSessionModelEntryKind
53-
{
54-
Unknown,
55-
Directory,
56-
File,
57-
SymbolicLink,
58-
Untracked,
59-
Problematic,
60-
PhantomDirectory,
61-
}
62-
63-
public sealed class SyncSessionModelEntry
64-
{
65-
public readonly SyncSessionModelEntryKind Kind;
66-
67-
// For Kind == Directory only.
68-
public readonly ReadOnlyDictionary<string, SyncSessionModelEntry> Contents;
69-
70-
// For Kind == File only.
71-
public readonly string Digest = "";
72-
public readonly bool Executable;
73-
74-
// For Kind = SymbolicLink only.
75-
public readonly string Target = "";
76-
77-
// For Kind = Problematic only.
78-
public readonly string Problem = "";
79-
80-
public SyncSessionModelEntry(Entry protoEntry)
81-
{
82-
Kind = protoEntry.Kind switch
83-
{
84-
EntryKind.Directory => SyncSessionModelEntryKind.Directory,
85-
EntryKind.File => SyncSessionModelEntryKind.File,
86-
EntryKind.SymbolicLink => SyncSessionModelEntryKind.SymbolicLink,
87-
EntryKind.Untracked => SyncSessionModelEntryKind.Untracked,
88-
EntryKind.Problematic => SyncSessionModelEntryKind.Problematic,
89-
EntryKind.PhantomDirectory => SyncSessionModelEntryKind.PhantomDirectory,
90-
_ => SyncSessionModelEntryKind.Unknown,
91-
};
92-
93-
switch (Kind)
94-
{
95-
case SyncSessionModelEntryKind.Directory:
96-
{
97-
var contents = new Dictionary<string, SyncSessionModelEntry>();
98-
foreach (var (key, value) in protoEntry.Contents)
99-
contents[key] = new SyncSessionModelEntry(value);
100-
Contents = new ReadOnlyDictionary<string, SyncSessionModelEntry>(contents);
101-
break;
102-
}
103-
case SyncSessionModelEntryKind.File:
104-
Digest = BitConverter.ToString(protoEntry.Digest.ToByteArray()).Replace("-", "").ToLower();
105-
Executable = protoEntry.Executable;
106-
break;
107-
case SyncSessionModelEntryKind.SymbolicLink:
108-
Target = protoEntry.Target;
109-
break;
110-
case SyncSessionModelEntryKind.Problematic:
111-
Problem = protoEntry.Problem;
112-
break;
113-
}
114-
}
115-
116-
public new string ToString()
117-
{
118-
var str = Kind.ToString();
119-
switch (Kind)
120-
{
121-
case SyncSessionModelEntryKind.Directory:
122-
str += $" ({Contents.Count} entries)";
123-
break;
124-
case SyncSessionModelEntryKind.File:
125-
str += $" ({Digest}, executable: {Executable})";
126-
break;
127-
case SyncSessionModelEntryKind.SymbolicLink:
128-
str += $" (target: {Target})";
129-
break;
130-
case SyncSessionModelEntryKind.Problematic:
131-
str += $" ({Problem})";
132-
break;
133-
}
134-
135-
return str;
136-
}
137-
}
138-
139-
public sealed class SyncSessionModelConflictChange
140-
{
141-
public readonly string Path; // relative to sync root
142-
143-
// null means non-existent:
144-
public readonly SyncSessionModelEntry? Old;
145-
public readonly SyncSessionModelEntry? New;
146-
147-
public SyncSessionModelConflictChange(Change protoChange)
148-
{
149-
Path = protoChange.Path;
150-
Old = protoChange.Old != null ? new SyncSessionModelEntry(protoChange.Old) : null;
151-
New = protoChange.New != null ? new SyncSessionModelEntry(protoChange.New) : null;
152-
}
153-
154-
public new string ToString()
155-
{
156-
const string nonExistent = "<non-existent>";
157-
var oldStr = Old != null ? Old.ToString() : nonExistent;
158-
var newStr = New != null ? New.ToString() : nonExistent;
159-
return $"{Path} ({oldStr} -> {newStr})";
160-
}
161-
}
162-
163-
public sealed class SyncSessionModelConflict
164-
{
165-
public readonly string Root; // relative to sync root
166-
public readonly List<SyncSessionModelConflictChange> AlphaChanges;
167-
public readonly List<SyncSessionModelConflictChange> BetaChanges;
168-
169-
public SyncSessionModelConflict(Conflict protoConflict)
170-
{
171-
Root = protoConflict.Root;
172-
AlphaChanges = protoConflict.AlphaChanges.Select(change => new SyncSessionModelConflictChange(change)).ToList();
173-
BetaChanges = protoConflict.BetaChanges.Select(change => new SyncSessionModelConflictChange(change)).ToList();
174-
}
175-
176-
private string? FriendlyProblem()
177-
{
178-
// If the change is <non-existent> -> !<non-existent>.
179-
if (AlphaChanges.Count == 1 && BetaChanges.Count == 1 &&
180-
AlphaChanges[0].Old == null &&
181-
BetaChanges[0].Old == null &&
182-
AlphaChanges[0].New != null &&
183-
BetaChanges[0].New != null)
184-
return
185-
"An entry was created on both endpoints and they do not match. You can resolve this conflict by deleting one of the entries on either side.";
186-
187-
return null;
188-
}
189-
190-
public string Description()
191-
{
192-
// This formatting is very similar to Mutagen.
193-
var str = $"Conflict at path '{Root}':";
194-
foreach (var change in AlphaChanges)
195-
str += $"\n (alpha) {change.ToString()}";
196-
foreach (var change in AlphaChanges)
197-
str += $"\n (beta) {change.ToString()}";
198-
if (FriendlyProblem() is { } friendlyProblem)
199-
str += $"\n\n {friendlyProblem}";
200-
201-
return str;
202-
}
203-
}
204-
20551
public class SyncSessionModel
20652
{
20753
public readonly string Identifier;
208-
public readonly string Name;
20954

21055
public readonly string AlphaName;
21156
public readonly string AlphaPath;
@@ -219,8 +64,8 @@ public class SyncSessionModel
21964
public readonly SyncSessionModelEndpointSize AlphaSize;
22065
public readonly SyncSessionModelEndpointSize BetaSize;
22166

222-
public readonly IReadOnlyList<SyncSessionModelConflict> Conflicts;
223-
public ulong OmittedConflicts;
67+
public readonly IReadOnlyList<string> Conflicts; // Conflict descriptions
68+
public readonly ulong OmittedConflicts;
22469
public readonly IReadOnlyList<string> Errors;
22570

22671
// If Paused is true, the session can be resumed. If false, the session can
@@ -231,10 +76,12 @@ public string StatusDetails
23176
{
23277
get
23378
{
234-
var str = $"{StatusString} ({StatusCategory})\n\n{StatusDescription}";
235-
foreach (var err in Errors) str += $"\n\nError: {err}";
236-
foreach (var conflict in Conflicts) str += $"\n\n{conflict.Description()}";
237-
if (OmittedConflicts > 0) str += $"\n\n{OmittedConflicts:N0} conflicts omitted";
79+
var str = StatusString;
80+
if (StatusCategory.ToString() != StatusString) str += $" ({StatusCategory})";
81+
str += $"\n\n{StatusDescription}";
82+
foreach (var err in Errors) str += $"\n\n-----\n\n{err}";
83+
foreach (var conflict in Conflicts) str += $"\n\n-----\n\n{conflict}";
84+
if (OmittedConflicts > 0) str += $"\n\n-----\n\n{OmittedConflicts:N0} conflicts omitted";
23885
return str;
23986
}
24087
}
@@ -252,7 +99,6 @@ public string SizeDetails
25299
public SyncSessionModel(State state)
253100
{
254101
Identifier = state.Session.Identifier;
255-
Name = state.Session.Name;
256102

257103
(AlphaName, AlphaPath) = NameAndPathFromUrl(state.Session.Alpha);
258104
(BetaName, BetaPath) = NameAndPathFromUrl(state.Session.Beta);
@@ -354,7 +200,7 @@ public SyncSessionModel(State state)
354200
StatusDescription = "The session has conflicts that need to be resolved.";
355201
}
356202

357-
Conflicts = state.Conflicts.Select(c => new SyncSessionModelConflict(c)).ToList();
203+
Conflicts = state.Conflicts.Select(ConflictToString).ToList();
358204
OmittedConflicts = state.ExcludedConflicts;
359205

360206
AlphaSize = new SyncSessionModelEndpointSize
@@ -403,4 +249,55 @@ private static (string, string) NameAndPathFromUrl(URL url)
403249

404250
return (name, path);
405251
}
252+
253+
private static string ConflictToString(Conflict conflict)
254+
{
255+
string? friendlyProblem = null;
256+
if (conflict.AlphaChanges.Count == 1 && conflict.BetaChanges.Count == 1 &&
257+
conflict.AlphaChanges[0].Old == null &&
258+
conflict.BetaChanges[0].Old == null &&
259+
conflict.AlphaChanges[0].New != null &&
260+
conflict.BetaChanges[0].New != null)
261+
friendlyProblem =
262+
"An entry was created on both endpoints and they do not match. You can resolve this conflict by deleting one of the entries on either side.";
263+
264+
var str = $"Conflict at path '{conflict.Root}':";
265+
foreach (var change in conflict.AlphaChanges)
266+
str += $"\n (alpha) {ChangeToString(change)}";
267+
foreach (var change in conflict.BetaChanges)
268+
str += $"\n (beta) {ChangeToString(change)}";
269+
if (friendlyProblem != null)
270+
str += $"\n\n{friendlyProblem}";
271+
272+
return str;
273+
}
274+
275+
private static string ChangeToString(Change change)
276+
{
277+
return $"{change.Path} ({EntryToString(change.Old)} -> {EntryToString(change.New)})";
278+
}
279+
280+
private static string EntryToString(Entry? entry)
281+
{
282+
if (entry == null) return "<non-existent>";
283+
var str = entry.Kind.ToString();
284+
switch (entry.Kind)
285+
{
286+
case EntryKind.Directory:
287+
str += $" ({entry.Contents.Count} entries)";
288+
break;
289+
case EntryKind.File:
290+
var digest = BitConverter.ToString(entry.Digest.ToByteArray()).Replace("-", "").ToLower();
291+
str += $" ({digest}, executable: {entry.Executable})";
292+
break;
293+
case EntryKind.SymbolicLink:
294+
str += $" (target: {entry.Target})";
295+
break;
296+
case EntryKind.Problematic:
297+
str += $" ({entry.Problem})";
298+
break;
299+
}
300+
301+
return str;
302+
}
406303
}

App/Services/MutagenController.cs

+40-25
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,55 @@
1818
using Grpc.Core;
1919
using Microsoft.Extensions.Options;
2020
using DaemonTerminateRequest = Coder.Desktop.MutagenSdk.Proto.Service.Daemon.TerminateRequest;
21+
using MutagenProtocol = Coder.Desktop.MutagenSdk.Proto.Url.Protocol;
2122
using SynchronizationTerminateRequest = Coder.Desktop.MutagenSdk.Proto.Service.Synchronization.TerminateRequest;
2223

2324
namespace Coder.Desktop.App.Services;
2425

25-
public class CreateSyncSessionRequest
26+
public enum CreateSyncSessionRequestEndpointProtocol
2627
{
27-
public Uri Alpha { get; init; }
28-
public Uri Beta { get; init; }
28+
Local,
29+
Ssh,
30+
}
2931

30-
public URL AlphaMutagenUrl => MutagenUrl(Alpha);
31-
public URL BetaMutagenUrl => MutagenUrl(Beta);
32+
public class CreateSyncSessionRequestEndpoint
33+
{
34+
public required CreateSyncSessionRequestEndpointProtocol Protocol { get; init; }
35+
public string User { get; init; } = "";
36+
public string Host { get; init; } = "";
37+
public uint Port { get; init; } = 0;
38+
public string Path { get; init; } = "";
3239

33-
private static URL MutagenUrl(Uri uri)
40+
public URL MutagenUrl
3441
{
35-
var protocol = uri.Scheme switch
36-
{
37-
"file" => Protocol.Local,
38-
"ssh" => Protocol.Ssh,
39-
_ => throw new ArgumentException("Only 'file' and 'ssh' URLs are supported", nameof(uri)),
40-
};
41-
42-
return new URL
43-
{
44-
Kind = Kind.Synchronization,
45-
Protocol = protocol,
46-
User = uri.UserInfo,
47-
Host = uri.Host,
48-
Port = uri.Port < 0 ? 0 : (uint)uri.Port,
49-
Path = protocol is Protocol.Local ? uri.LocalPath : uri.AbsolutePath,
50-
};
42+
get
43+
{
44+
var protocol = Protocol switch
45+
{
46+
CreateSyncSessionRequestEndpointProtocol.Local => MutagenProtocol.Local,
47+
CreateSyncSessionRequestEndpointProtocol.Ssh => MutagenProtocol.Ssh,
48+
_ => throw new ArgumentException($"Invalid protocol '{Protocol}'", nameof(Protocol)),
49+
};
50+
51+
return new URL
52+
{
53+
Kind = Kind.Synchronization,
54+
Protocol = protocol,
55+
User = User,
56+
Host = Host,
57+
Port = Port,
58+
Path = Path,
59+
};
60+
}
5161
}
5262
}
5363

64+
public class CreateSyncSessionRequest
65+
{
66+
public required CreateSyncSessionRequestEndpoint Alpha { get; init; }
67+
public required CreateSyncSessionRequestEndpoint Beta { get; init; }
68+
}
69+
5470
public interface ISyncSessionController : IAsyncDisposable
5571
{
5672
Task<IEnumerable<SyncSessionModel>> ListSyncSessions(CancellationToken ct = default);
@@ -152,8 +168,8 @@ public async Task<SyncSessionModel> CreateSyncSession(CreateSyncSessionRequest r
152168
Prompter = prompter.Identifier,
153169
Specification = new CreationSpecification
154170
{
155-
Alpha = req.AlphaMutagenUrl,
156-
Beta = req.BetaMutagenUrl,
171+
Alpha = req.Alpha.MutagenUrl,
172+
Beta = req.Beta.MutagenUrl,
157173
// TODO: probably should set these at some point
158174
Configuration = new Configuration(),
159175
ConfigurationAlpha = new Configuration(),
@@ -637,7 +653,6 @@ await _dup.RequestStream.WriteAsync(new HostRequest
637653
catch
638654
{
639655
await _dup.RequestStream.CompleteAsync();
640-
_dup.Dispose();
641656
// TODO: log?
642657
}
643658
}

0 commit comments

Comments
 (0)