11using System . Text . Json ;
22using SIL . Harmony . Core ;
33using Microsoft . EntityFrameworkCore ;
4+ using Microsoft . Extensions . Logging ;
45using Microsoft . Extensions . Options ;
56using SIL . Harmony . Changes ;
67using SIL . Harmony . Db ;
@@ -22,17 +23,20 @@ public class DataModel : ISyncable, IAsyncDisposable
2223 private readonly JsonSerializerOptions _serializerOptions ;
2324 private readonly IHybridDateTimeProvider _timeProvider ;
2425 private readonly IOptions < CrdtConfig > _crdtConfig ;
26+ private readonly ILogger < DataModel > _logger ;
2527
2628 //constructor must be internal because CrdtRepository is internal
2729 internal DataModel ( CrdtRepository crdtRepository ,
2830 JsonSerializerOptions serializerOptions ,
2931 IHybridDateTimeProvider timeProvider ,
30- IOptions < CrdtConfig > crdtConfig )
32+ IOptions < CrdtConfig > crdtConfig ,
33+ ILogger < DataModel > logger )
3134 {
3235 _crdtRepository = crdtRepository ;
3336 _serializerOptions = serializerOptions ;
3437 _timeProvider = timeProvider ;
3538 _crdtConfig = crdtConfig ;
39+ _logger = logger ;
3640 }
3741
3842
@@ -131,19 +135,48 @@ private static ChangeEntity<IChange> ToChangeEntity(IChange change, int index)
131135 async Task ISyncable . AddRangeFromSync ( IEnumerable < Commit > commits )
132136 {
133137 commits = commits . ToArray ( ) ;
134- _timeProvider . TakeLatestTime ( commits . Select ( c => c . HybridDateTime ) ) ;
135- var ( oldestChange , newCommits ) = await _crdtRepository . FilterExistingCommits ( commits . ToArray ( ) ) ;
136- //no changes added
137- if ( oldestChange is null || newCommits is [ ] ) return ;
138-
139- await using var transaction = await _crdtRepository . BeginTransactionAsync ( ) ;
140- //if there are deferred commits, update snapshots with them first
141- if ( _deferredCommits is not [ ] ) await UpdateSnapshotsByDeferredCommits ( ) ;
142- //don't save since UpdateSnapshots will also modify newCommits with hashes, so changes will be saved once that's done
143- await _crdtRepository . AddCommits ( newCommits , false ) ;
144- await UpdateSnapshots ( oldestChange , newCommits ) ;
145- await ValidateCommits ( ) ;
146- await transaction . CommitAsync ( ) ;
138+ try
139+ {
140+ _timeProvider . TakeLatestTime ( commits . Select ( c => c . HybridDateTime ) ) ;
141+ var ( oldestChange , newCommits ) = await _crdtRepository . FilterExistingCommits ( commits . ToArray ( ) ) ;
142+ //no changes added
143+ if ( oldestChange is null || newCommits is [ ] ) return ;
144+
145+ await using var transaction = await _crdtRepository . BeginTransactionAsync ( ) ;
146+ //if there are deferred commits, update snapshots with them first
147+ if ( _deferredCommits is not [ ] ) await UpdateSnapshotsByDeferredCommits ( ) ;
148+ //don't save since UpdateSnapshots will also modify newCommits with hashes, so changes will be saved once that's done
149+ await _crdtRepository . AddCommits ( newCommits , false ) ;
150+ await UpdateSnapshots ( oldestChange , newCommits ) ;
151+ await ValidateCommits ( ) ;
152+ await transaction . CommitAsync ( ) ;
153+ }
154+ catch ( DbUpdateException e )
155+ {
156+ _logger . LogError ( e , "Failed to sync commits, check {FailedImportPath} for more details" , _crdtConfig . Value . FailedSyncOutputPath ) ;
157+ await DumpFailedSync ( new
158+ {
159+ ExceptionMessage = e . ToString ( ) ,
160+ Commits = commits . DefaultOrder ( ) ,
161+ Objects = e . Entries . Select ( entry => entry . Entity )
162+ } ) ;
163+ throw ;
164+ }
165+ }
166+
167+ private async Task DumpFailedSync ( object data )
168+ {
169+ try
170+ {
171+ Directory . CreateDirectory ( _crdtConfig . Value . FailedSyncOutputPath ) ;
172+ await using var failedImport =
173+ File . Create ( Path . Combine ( _crdtConfig . Value . FailedSyncOutputPath , "last-failed-import.json" ) ) ;
174+ await JsonSerializer . SerializeAsync ( failedImport , data , _serializerOptions ) ;
175+ }
176+ catch ( Exception e )
177+ {
178+ _logger . LogError ( e , "Failed to dump failed import" ) ;
179+ }
147180 }
148181
149182 ValueTask < bool > ISyncable . ShouldSync ( )
0 commit comments