11using System ;
22using System . Collections . Generic ;
3- using System . Collections . ObjectModel ;
43using System . Linq ;
54using Coder . Desktop . App . Converters ;
65using 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-
20551public 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 \n Error: { 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}
0 commit comments