|
3 | 3 | * PROJECT: ExtendedSystemObjects |
4 | 4 | * FILE: ExtendedSystemObjects/TransactionLogs.cs |
5 | 5 | * PURPOSE: Basic Transaction Log, log Changes |
6 | | - * PROGRAMER: Peter Geinitz (Wayfarer) |
| 6 | + * PROGRAMMER: Peter Geinitz (Wayfarer) |
| 7 | + * DESCRIPTION: |
| 8 | + * Basic Transaction Log for tracking Add, Remove, and Change operations with unique entries. |
| 9 | + * Designed as a minimal, thread-safe utility for logging changes in collections or objects. |
| 10 | + * |
| 11 | + * This class provides a bare-bones implementation intended for adaptation to specific use cases. |
| 12 | + * It focuses on tracking state changes without assumptions about persistence or complex undo-redo mechanisms. |
| 13 | + * |
| 14 | + * Usage Notes: |
| 15 | + * - Thread-safe via internal locking and concurrent dictionary. |
| 16 | + * - Uses integer keys managed internally; may need customization for key management. |
| 17 | + * - Does not enforce validation beyond basic checks; users should adapt as needed. |
| 18 | + * - Intended primarily for lightweight change tracking and simple transaction logging scenarios. |
| 19 | + * |
| 20 | + * Future improvements could include: |
| 21 | + * - Persistence support |
| 22 | + * - Undo/Redo operations |
| 23 | + * - More advanced query/filtering of logs |
| 24 | + * - Custom key strategies or identifiers |
7 | 25 | */ |
8 | 26 |
|
9 | 27 | // ReSharper disable MemberCanBeInternal |
|
17 | 35 | namespace ExtendedSystemObjects |
18 | 36 | { |
19 | 37 | /// <summary> |
20 | | - /// Basic Transaction Log with unique entries and generic entries |
| 38 | + /// Basic Transaction Log with unique entries and generic entries. |
| 39 | + /// Supports Add, Change, and Remove logging. |
21 | 40 | /// </summary> |
22 | 41 | public sealed class TransactionLogs |
23 | 42 | { |
24 | 43 | /// <summary> |
25 | | - /// The lock for non-thread-safe operations. |
| 44 | + /// The lock used to synchronize operations that require thread safety beyond what the ConcurrentDictionary provides. |
26 | 45 | /// </summary> |
27 | 46 | private readonly object _lock = new(); |
28 | 47 |
|
29 | | - private int _changedFlag; // 0 = false, 1 = true |
| 48 | + /// <summary> |
| 49 | + /// Flag used to track whether the changelog has been modified. |
| 50 | + /// </summary> |
| 51 | + private int _changedFlag; |
30 | 52 |
|
31 | 53 | /// <summary> |
32 | | - /// Initializes a new instance of the <see cref="TransactionLogs" /> class. |
| 54 | + /// Initializes a new instance of the <see cref="TransactionLogs"/> class. |
33 | 55 | /// </summary> |
34 | 56 | public TransactionLogs() |
35 | 57 | { |
36 | 58 | Changelog = new ConcurrentDictionary<int, LogEntry>(); |
37 | 59 | } |
38 | 60 |
|
39 | 61 | /// <summary> |
40 | | - /// The changelog |
| 62 | + /// Gets the changelog containing all tracked operations. |
41 | 63 | /// </summary> |
42 | 64 | public ConcurrentDictionary<int, LogEntry> Changelog { get; } |
43 | 65 |
|
44 | 66 | /// <summary> |
45 | | - /// Gets a value indicating whether this <see cref="TransactionLogs" /> is changed. |
| 67 | + /// Gets a value indicating whether any changes have been logged. |
46 | 68 | /// </summary> |
47 | | - /// <value> |
48 | | - /// <c>true</c> if changed; currentSequence-wise, <c>false</c>. |
49 | | - /// </value> |
50 | 69 | public bool Changed |
51 | 70 | { |
52 | 71 | get => Interlocked.CompareExchange(ref _changedFlag, 0, 0) == 1; |
53 | 72 | private set => Interlocked.Exchange(ref _changedFlag, value ? 1 : 0); |
54 | 73 | } |
55 | 74 |
|
56 | 75 | /// <summary> |
57 | | - /// Adds the specified unique identifier. |
| 76 | + /// Adds a new log entry for an object. |
58 | 77 | /// </summary> |
59 | | - /// <param name="uniqueIdentifier">The unique identifier.</param> |
60 | | - /// <param name="item">The item.</param> |
61 | | - /// <param name="startData">if set to <c>true</c> [start data].</param> |
| 78 | + /// <param name="uniqueIdentifier">The unique identifier of the object.</param> |
| 79 | + /// <param name="item">The object being added.</param> |
| 80 | + /// <param name="startData">Whether this is initial state data (i.e., pre-existing before logging).</param> |
62 | 81 | public void Add(int uniqueIdentifier, object item, bool startData) |
63 | 82 | { |
64 | 83 | lock (_lock) |
65 | 84 | { |
66 | 85 | var log = new LogEntry |
67 | 86 | { |
68 | | - State = LogState.Add, Data = item, UniqueIdentifier = uniqueIdentifier, StartData = startData |
| 87 | + State = LogState.Add, |
| 88 | + Data = item, |
| 89 | + UniqueIdentifier = uniqueIdentifier, |
| 90 | + StartData = startData |
69 | 91 | }; |
70 | 92 |
|
71 | | - // Add the new log entry to the Changelog |
72 | | - Changelog[Changelog.Count] = log; // Using index as the key |
| 93 | + Changelog[GetNewKey()] = log; |
73 | 94 | Changed = true; |
74 | 95 | } |
75 | 96 | } |
76 | 97 |
|
77 | 98 | /// <summary> |
78 | | - /// Removes the specified unique identifier. |
| 99 | + /// Adds a remove log entry for an object if an 'Add' entry exists for it. |
79 | 100 | /// </summary> |
80 | | - /// <param name="uniqueIdentifier">The unique identifier.</param> |
| 101 | + /// <param name="uniqueIdentifier">The unique identifier of the object.</param> |
81 | 102 | public void Remove(int uniqueIdentifier) |
82 | 103 | { |
83 | 104 | lock (_lock) |
84 | 105 | { |
85 | 106 | var id = GetItem(uniqueIdentifier, LogState.Add); |
86 | | - if (id != -1) |
| 107 | + if (id == -1) return; |
| 108 | + |
| 109 | + var item = Changelog[id].Data; |
| 110 | + |
| 111 | + Changelog[GetNewKey()] = new LogEntry |
87 | 112 | { |
88 | | - var item = Changelog[id].Data; |
89 | | - var log = new LogEntry |
90 | | - { |
91 | | - State = LogState.Remove, Data = item, UniqueIdentifier = uniqueIdentifier |
92 | | - }; |
93 | | - Changelog[Changelog.Count] = log; // Add remove log entry |
94 | | - Changed = true; |
95 | | - } |
| 113 | + State = LogState.Remove, |
| 114 | + Data = item, |
| 115 | + UniqueIdentifier = uniqueIdentifier |
| 116 | + }; |
| 117 | + |
| 118 | + Changed = true; |
96 | 119 | } |
97 | 120 | } |
98 | 121 |
|
99 | 122 | /// <summary> |
100 | | - /// Changes the specified unique identifier. |
| 123 | + /// Logs a change to an object, if the object has changed. |
| 124 | + /// Updates an existing change entry if one exists and differs. |
101 | 125 | /// </summary> |
102 | | - /// <param name="uniqueIdentifier">The unique identifier.</param> |
103 | | - /// <param name="item">The item.</param> |
| 126 | + /// <param name="uniqueIdentifier">The unique identifier of the object.</param> |
| 127 | + /// <param name="item">The updated object.</param> |
104 | 128 | public void Change(int uniqueIdentifier, object item) |
105 | 129 | { |
106 | 130 | lock (_lock) |
107 | 131 | { |
108 | | - if (GetItem(uniqueIdentifier, LogState.Change) is var entry && entry != -1) |
| 132 | + var entry = GetItem(uniqueIdentifier, LogState.Change); |
| 133 | + |
| 134 | + if (entry != -1 && Changelog[entry].Data.Equals(item) == false) |
109 | 135 | { |
110 | | - var existingItem = Changelog[entry]; |
111 | | - if (!existingItem.Data.Equals(item)) |
| 136 | + Changelog[entry] = new LogEntry |
112 | 137 | { |
113 | | - Changelog[entry] = new LogEntry |
114 | | - { |
115 | | - State = LogState.Change, Data = item, UniqueIdentifier = uniqueIdentifier |
116 | | - }; |
117 | | - Changed = true; |
118 | | - } |
| 138 | + State = LogState.Change, |
| 139 | + Data = item, |
| 140 | + UniqueIdentifier = uniqueIdentifier |
| 141 | + }; |
| 142 | + Changed = true; |
119 | 143 | } |
120 | | - else |
| 144 | + else if (entry == -1) |
121 | 145 | { |
122 | | - var log = new LogEntry |
| 146 | + Changelog[GetNewKey()] = new LogEntry |
123 | 147 | { |
124 | | - State = LogState.Change, Data = item, UniqueIdentifier = uniqueIdentifier |
| 148 | + State = LogState.Change, |
| 149 | + Data = item, |
| 150 | + UniqueIdentifier = uniqueIdentifier |
125 | 151 | }; |
126 | | - Changelog[Changelog.Count] = log; // Add change log entry |
127 | 152 | Changed = true; |
128 | 153 | } |
129 | 154 | } |
130 | 155 | } |
131 | 156 |
|
132 | 157 | /// <summary> |
133 | | - /// Gets the predecessor of a given entry. |
| 158 | + /// Gets the predecessor Add entry key for a given log entry key, if available. |
134 | 159 | /// </summary> |
135 | | - /// <param name="id">The identifier.</param> |
136 | | - /// <returns>The predecessor's key or -1 if not found.</returns> |
| 160 | + /// <param name="id">The key of the log entry to search from.</param> |
| 161 | + /// <returns>The key of the matching Add entry, or -1 if not found.</returns> |
137 | 162 | public int GetPredecessor(int id) |
138 | 163 | { |
139 | 164 | lock (_lock) |
140 | 165 | { |
141 | | - var unique = Changelog[id].UniqueIdentifier; |
| 166 | + if (!Changelog.TryGetValue(id, out var reference)) return -1; |
| 167 | + var unique = reference.UniqueIdentifier; |
142 | 168 |
|
143 | | - foreach (var item in Changelog.Reverse().Where(item => |
144 | | - item.Key < id && item.Value.UniqueIdentifier == unique && |
145 | | - item.Value.State == LogState.Add)) |
| 169 | + foreach (var item in Changelog.Reverse()) |
146 | 170 | { |
147 | | - return item.Key; |
| 171 | + if (item.Key >= id) continue; |
| 172 | + |
| 173 | + if (item.Value.UniqueIdentifier == unique && item.Value.State == LogState.Add) |
| 174 | + return item.Key; |
148 | 175 | } |
149 | 176 |
|
150 | 177 | return -1; |
151 | 178 | } |
152 | 179 | } |
153 | 180 |
|
154 | 181 | /// <summary> |
155 | | - /// Gets the new items. |
| 182 | + /// Gets all newly added items (i.e., those not marked as StartData). |
156 | 183 | /// </summary> |
157 | | - /// <returns>Newly added items</returns> |
| 184 | + /// <returns>A dictionary of new items or null if none exist.</returns> |
158 | 185 | public Dictionary<int, LogEntry> GetNewItems() |
159 | 186 | { |
160 | 187 | lock (_lock) |
161 | 188 | { |
162 | | - return Changelog.Count == 0 |
163 | | - ? null |
164 | | - : Changelog.Where(item => !item.Value.StartData).ToDictionary(item => item.Key, item => item.Value); |
| 189 | + if (Changelog.IsEmpty) |
| 190 | + return null; |
| 191 | + |
| 192 | + return Changelog |
| 193 | + .Where(entry => !entry.Value.StartData) |
| 194 | + .ToDictionary(entry => entry.Key, entry => entry.Value); |
165 | 195 | } |
166 | 196 | } |
167 | 197 |
|
168 | 198 | /// <summary> |
169 | | - /// Gets the item for a specific state. |
| 199 | + /// Gets the most recent entry matching the specified unique identifier and state. |
170 | 200 | /// </summary> |
171 | | - /// <param name="uniqueIdentifier">The unique identifier.</param> |
172 | | - /// <param name="state">State of Item</param> |
173 | | - /// <returns>ChangedItem</returns> |
| 201 | + /// <param name="uniqueIdentifier">The unique identifier of the object.</param> |
| 202 | + /// <param name="state">The state to match (Add, Remove, Change).</param> |
| 203 | + /// <returns>The key of the matching entry or -1 if not found.</returns> |
174 | 204 | internal int GetItem(int uniqueIdentifier, LogState state) |
175 | 205 | { |
176 | 206 | lock (_lock) |
177 | 207 | { |
178 | | - if (Changelog == null || Changelog.Count == 0) |
179 | | - { |
| 208 | + if (Changelog.IsEmpty) |
180 | 209 | return -1; |
181 | | - } |
182 | 210 |
|
183 | 211 | foreach (var (key, value) in Changelog.Reverse()) |
184 | 212 | { |
185 | 213 | if (value.UniqueIdentifier == uniqueIdentifier && value.State == state) |
186 | | - { |
187 | 214 | return key; |
188 | | - } |
189 | 215 | } |
190 | 216 |
|
191 | 217 | return -1; |
192 | 218 | } |
193 | 219 | } |
194 | 220 |
|
195 | 221 | /// <summary> |
196 | | - /// Gets the next available key. |
| 222 | + /// Gets the next available key in the changelog. |
197 | 223 | /// </summary> |
198 | | - /// <returns>First available Key</returns> |
| 224 | + /// <returns>The first unused integer key.</returns> |
199 | 225 | public int GetNewKey() |
200 | 226 | { |
201 | 227 | lock (_lock) |
202 | 228 | { |
203 | | - if (Changelog?.IsEmpty != false) |
204 | | - { |
| 229 | + if (Changelog.IsEmpty) |
205 | 230 | return 0; |
206 | | - } |
207 | 231 |
|
208 | | - var lst = Changelog.Keys.ToList(); |
209 | | - return Utility.GetFirstAvailableIndex(lst); |
| 232 | + var keys = Changelog.Keys.ToList(); |
| 233 | + return Utility.GetFirstAvailableIndex(keys); |
210 | 234 | } |
211 | 235 | } |
212 | 236 | } |
|
0 commit comments