• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

HicServices / RDMP / 7102803153

05 Dec 2023 03:19PM UTC coverage: 56.338% (-0.4%) from 56.745%
7102803153

push

github

web-flow
Bump NUnit from 3.14.0 to 4.0.0 (#1686)

10562 of 20305 branches covered (0.0%)

Branch coverage included in aggregate %.

64 of 96 new or added lines in 21 files covered. (66.67%)

161 existing lines in 17 files now uncovered.

30382 of 52370 relevant lines covered (58.01%)

7337.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.56
/Rdmp.Core/Curation/Data/CommitInProgress.cs
1
// Copyright (c) The University of Dundee 2018-2019
2
// This file is part of the Research Data Management Platform (RDMP).
3
// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4
// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5
// You should have received a copy of the GNU General Public License along with RDMP. If not, see <https://www.gnu.org/licenses/>.
6

7
using System;
8
using System.Collections.Generic;
9
using System.Linq;
10
using System.Runtime.InteropServices.ComTypes;
11
using Rdmp.Core.CommandExecution;
12
using Rdmp.Core.MapsDirectlyToDatabaseTable;
13
using Rdmp.Core.Repositories;
14
using YamlDotNet.Serialization;
15

16
namespace Rdmp.Core.Curation.Data;
17

18
/// <summary>
19
/// Tracks changes made by the user to one or more objects.  Changes are tracked from
20
/// the moment the class is constructed until <see cref="TryFinish(IBasicActivateItems)"/>
21
/// completes successfully.  This helps with cancellation
22
/// </summary>
23
public class CommitInProgress : IDisposable
24
{
25
    private Dictionary<IMapsDirectlyToDatabaseTable, MementoInProgress> originalStates = new();
4✔
26

27
    public CommitInProgressSettings Settings { get; }
12✔
28

29
    private IRDMPPlatformRepositoryServiceLocator _locator;
30
    private List<IRepository> _repositories;
31
    private ISerializer _serializer;
32

33
    /// <summary>
34
    /// True when <see cref="TryFinish(IBasicActivateItems)"/> is confirmed to definitely be happening.  Controls
35
    /// save suppression
36
    /// </summary>
37
    private bool _finishing;
38

39
    private bool _isDisposed;
40

41
    /// <summary>
42
    /// Starts progress towards creating a <see cref="Commit"/>.
43
    /// </summary>
44
    /// <param name="locator"></param>
45
    /// <param name="settings"></param>
46
    public CommitInProgress(IRDMPPlatformRepositoryServiceLocator locator, CommitInProgressSettings settings)
4✔
47
    {
48
        Settings = settings;
4✔
49

50
        _locator = locator;
4✔
51

52
        _repositories = _locator.GetAllRepositories().ToList();
4✔
53
        _serializer = YamlRepository.CreateSerializer(_repositories.SelectMany(r => r.GetCompatibleTypes()).Distinct()
12✔
54
        );
4✔
55

56
        foreach (var repo in _repositories)
24✔
57
        {
58
            repo.Deleting += Deleting;
8✔
59
            repo.Inserting += Inserting;
8✔
60

61
            if (settings.UseTransactions)
8✔
62
                // these get cleaned up in Dispose or TryFinish
63
                repo.BeginNewTransaction();
4✔
64
        }
65

66
        foreach (var t in settings.ObjectsToTrack)
16✔
67
            originalStates.Add(t, new MementoInProgress(t, _serializer.Serialize(t)));
4✔
68
    }
4✔
69

70
    private void Inserting(object sender, IMapsDirectlyToDatabaseTableEventArgs e)
71
    {
72
        if (_finishing)
8!
73
            return;
8✔
74

75
        // if we are not in global mode with transactions then this is a long term
76
        // commit (e.g. user has tab open for half an hour).  Don't hoover up all created/deleted objects
77
        if (!Settings.UseTransactions) return;
×
78

79
        // how can we be tracking an object that was not created yet?
80
        if (originalStates.TryGetValue(e.Object, out var state))
×
81
            // oh well just pretend it magicked into existence
82
            state.Type = MementoType.Add;
×
83
        else
84
            // legit new object we didn't know about before
85
            originalStates.Add(e.Object, new MementoInProgress(e.Object, null)
×
86
            {
×
87
                Type = MementoType.Add
×
88
            });
×
89
    }
×
90

91
    private void Deleting(object sender, IMapsDirectlyToDatabaseTableEventArgs e)
92
    {
93
        if (_finishing)
×
94
            return;
×
95

96
        // if we are not in global mode with transactions then this is a long term
97
        // commit (e.g. user has tab open for half an hour).  Don't hoover up all created/deleted objects
98
        if (!Settings.UseTransactions) return;
×
99

100
        // one of the objects we are tracking has been deleted
NEW
101
        if (originalStates.TryGetValue(e.Object, out var inProgress))
×
102
        {
103
            // change our understanding of this object
104

NEW
105
            if (inProgress.Type == MementoType.Add)
×
106
                // ok user created this object during the commit then deleted it again... odd but fair enough
107
                // pretend it never existed
108
                originalStates.Remove(e.Object);
×
109
            else
NEW
110
                inProgress.Type = MementoType.Delete;
×
111
        }
112
        else
113
        {
114
            // an object we are not yet tracking has been deleted
115
            originalStates.Add(e.Object, new MementoInProgress(e.Object, _serializer.Serialize(e.Object))
×
116
            {
×
117
                Type = MementoType.Delete
×
118
            });
×
119
        }
120
    }
×
121

122
    /// <summary>
123
    /// Returns a new <see cref="Commit"/> or null if nothing has changed with
124
    /// the tracked objects since construction.
125
    /// </summary>
126
    /// <returns></returns>
127
    public Commit TryFinish(IBasicActivateItems activator)
128
    {
129
        if (_finishing)
4!
130
            throw new ObjectDisposedException(
×
131
                $"{nameof(CommitInProgress)} has already been successfully finished and shutdown",
×
132
                nameof(CommitInProgress));
×
133

134
        if (_isDisposed)
4!
135
            throw new ObjectDisposedException(nameof(CommitInProgress));
×
136

137
        var changes = new Dictionary<IMapsDirectlyToDatabaseTable, Tuple<MementoInProgress, string>>();
4✔
138

139
        foreach (var t in originalStates)
16✔
140
        {
141
            // serialize the current state on finishing into yaml (or use null for deleted objects)
142
            var newYaml = t.Value.Type == MementoType.Delete ? null : _serializer.Serialize(t.Key);
4!
143

144
            //something changed
145
            if (newYaml != t.Value.OldYaml) changes.Add(t.Key, Tuple.Create(t.Value, newYaml));
6✔
146
        }
147

148
        if (!changes.Any())
4✔
149
            // no changes so no need for a Commit
150
            return null;
2✔
151
        var cataRepo = _locator.CatalogueRepository;
2✔
152

153
        var description = GetDescription(changes);
2✔
154
        var transaction = Guid.NewGuid();
2✔
155

156
        if (activator.IsInteractive)
2!
157
        {
158
            // object name or count of the number of objects
159
            var collectionDescription =
×
160
                changes.Count == 1 ? changes.Single().Key.ToString() : $"{changes.Count} object(s)";
×
161

162
            if (activator.TypeText(new DialogArgs
×
163
            {
×
164
                WindowTitle = transaction.ToString(),
×
165
                TaskDescription = $"Enter a description of what changes you have made to {collectionDescription}"
×
166
            }, int.MaxValue, description, out var newDescription, false))
×
167
                description = newDescription;
×
168
            else
169
                // user cancelled creating Commit
170
                return null;
×
171
        }
172

173
        // We couldn't describe the changes, that's bad...
174
        if (description == null)
2!
175
            return null;
×
176

177
        // Ok user has typed in a description (or system generated one) and we are
178
        // definitely going to do this
179

180
        _finishing = true;
2✔
181

182
        var c = new Commit(cataRepo, transaction, description);
2✔
183

184
        foreach (var m in changes.OrderBy(c => c.Value.Item1.Order))
10✔
185
            _ = new Memento(cataRepo, c, m.Value.Item1.Type, m.Key, m.Value.Item1.OldYaml, m.Value.Item2);
2✔
186

187
        // if we created a bunch of db transactions (one per database/server known about) for this commit
188
        // then we should be letting these changes go ahead
189
        if (Settings.UseTransactions)
2!
190
            foreach (var repo in _repositories)
×
191
                repo.EndTransaction(true);
×
192

193
        return c;
2✔
194
    }
195

196
    private string GetDescription(Dictionary<IMapsDirectlyToDatabaseTable, Tuple<MementoInProgress, string>> changes)
197
    {
198
        // no changes
199
        if (changes.Count == 0)
2!
200
            return null;
×
201

202
        if (Settings.Description != null) return Settings.Description;
2!
203

204
        // we can't summarise changes to multiple objects
205
        if (changes.Count != 1) return "TODO";
2!
206

207
        var kv = changes.Single();
2✔
208
        var props = kv.Value.Item1.GetDiffProperties(kv.Key).ToArray();
2✔
209

210
        // no visible changes... but yaml is different which is odd.
211
        // Either way abandon this commit.
212
        return !props.Any()
2!
213
            ? null
2✔
214
            : $"Update {kv.Key.GetType().Name} {string.Join(", ", props.Select(p => p.Name).ToArray())}";
6✔
215
    }
216

217
    public void Dispose()
218
    {
219
        GC.SuppressFinalize(this);
4✔
220
        foreach (var repo in _repositories)
24✔
221
        {
222
            repo.Deleting += Deleting;
8✔
223
            repo.Inserting += Inserting;
8✔
224

225

226
            if (Settings.UseTransactions && _finishing == false)
8✔
227
                try
228
                {
229
                    // Abandon transactions
230
                    repo.EndTransaction(false);
4✔
231
                }
4✔
232
                catch (NotSupportedException)
×
233
                {
234
                    // ok maybe someone else shut this down somehow... whatever
235
                }
×
236
        }
237

238
        _isDisposed = true;
4✔
239
    }
4✔
240
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc