-
Notifications
You must be signed in to change notification settings - Fork 83
Expand file tree
/
Copy pathLabelFile.cs
More file actions
288 lines (252 loc) · 7.48 KB
/
LabelFile.cs
File metadata and controls
288 lines (252 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Waher.Runtime.Threading;
namespace Waher.Persistence.Files
{
/// <summary>
/// Maintains an enumerated set of labels for an Object B-Tree File.
/// </summary>
public class LabelFile : SerialFile
{
private readonly Dictionary<string, uint> codesByLabel = new Dictionary<string, uint>();
private readonly Dictionary<uint, string> labelsByCode = new Dictionary<uint, string>();
private readonly MultiReadSingleWriteObject synchObj;
private readonly int timeoutMilliseconds;
private LabelFile(string FileName, string CollectionName, int TimeoutMilliseconds, bool Encrypted)
: base(FileName, CollectionName, Encrypted)
{
this.synchObj = new MultiReadSingleWriteObject(this, false);
this.timeoutMilliseconds = TimeoutMilliseconds;
}
/// <summary>
/// Reads a label from the file.
/// </summary>
/// <param name="Position">Position of label.</param>
/// <returns>Label, and the position of the following label.</returns>
public async Task<KeyValuePair<string, long>> ReadLabel(long Position)
{
KeyValuePair<byte[], long> P = await this.ReadBlock(Position);
return new KeyValuePair<string, long>(Encoding.UTF8.GetString(P.Key), P.Value);
}
/// <summary>
/// Writes a label to the end of the file.
/// </summary>
/// <param name="Label">New label to save to the file.</param>
/// <returns>Position of label.</returns>
public Task<long> WriteLabel(string Label)
{
return this.WriteBlock(Encoding.UTF8.GetBytes(Label));
}
/// <summary>
/// Writes a label to the end of the file.
/// </summary>
/// <param name="Label">New label to save to the file.</param>
/// <returns>Position of label.</returns>
protected Task<long> WriteLabelLocked(string Label)
{
return this.WriteBlockLocked(Encoding.UTF8.GetBytes(Label));
}
/// <summary>
/// Creates a LabelFile object.
/// </summary>
/// <param name="CollectionName">Name of collection.</param>
/// <param name="TimeoutMilliseconds">Timeout, in milliseconds, to wait for access to the database layer.</param>
/// <param name="Encrypted">If the files should be encrypted or not.</param>
/// <param name="Provider">Reference to the files provider.</param>
/// <returns>LabelFile object.</returns>
public static async Task<LabelFile> Create(string CollectionName, int TimeoutMilliseconds, bool Encrypted, FilesProvider Provider)
{
string FileName = Provider.GetFileName(CollectionName);
string LabelsFileName = FileName + ".labels";
bool LabelsExists = File.Exists(LabelsFileName);
LabelFile Result = new LabelFile(LabelsFileName, CollectionName, TimeoutMilliseconds, Encrypted);
uint LastCode = 0;
uint Code = 1;
await GetKeys(Result, Provider);
if (LabelsExists)
{
long Len = await Result.GetLength();
long Pos = 0;
while (Pos < Len)
{
try
{
KeyValuePair<string, long> P = await Result.ReadLabel(Pos);
Result.codesByLabel[P.Key] = Code;
Result.labelsByCode[Code] = P.Key;
LastCode = Code;
Code++;
Pos = P.Value;
}
catch (Exception)
{
await Result.Truncate(Pos);
Len = Pos;
}
}
}
else
{
string NamesFileName = FileName + ".names";
if (File.Exists(NamesFileName))
{
SortedDictionary<uint, string> NewCodes = null;
using (StringDictionary Names = await StringDictionary.Create(NamesFileName, string.Empty, CollectionName, Provider, false))
{
foreach (KeyValuePair<string, object> Rec in await Names.ToArrayAsync())
{
if (Rec.Value is ulong Code2 && Code2 <= uint.MaxValue && !Result.labelsByCode.ContainsKey(Code = (uint)Code2))
{
if (NewCodes is null)
NewCodes = new SortedDictionary<uint, string>();
Result.codesByLabel[Rec.Key] = Code;
Result.labelsByCode[Code] = Rec.Key;
NewCodes[Code] = Rec.Key;
}
}
}
while (!(NewCodes is null))
{
LastCode++;
if (NewCodes.TryGetValue(LastCode, out string Label))
{
await Result.WriteLabel(Label);
NewCodes.Remove(LastCode);
if (NewCodes.Count == 0)
NewCodes = null;
}
else
await Result.WriteLabel(LastCode.ToString());
}
File.Delete(NamesFileName);
}
}
return Result;
}
private async Task LockRead()
{
if (!await this.synchObj.TryBeginRead(this.timeoutMilliseconds))
throw new TimeoutException("Unable to get read access to label file for " + this.collectionName + ".");
}
private Task EndRead()
{
return this.synchObj.EndRead();
}
private async Task LockWrite()
{
if (!await this.synchObj.TryBeginWrite(this.timeoutMilliseconds))
throw new TimeoutException("Unable to get write access to label file for " + this.collectionName + ".");
}
private Task EndWrite()
{
return this.synchObj.EndWrite();
}
/// <summary>
/// Gets the code for a specific field in a collection.
/// </summary>
/// <param name="FieldName">Name of field.</param>
/// <returns>Field code.</returns>
public async Task<uint> GetFieldCode(string FieldName)
{
uint Result;
await this.LockRead();
try
{
if (this.codesByLabel.TryGetValue(FieldName, out Result))
return Result;
}
finally
{
await this.EndRead();
}
await this.LockWrite();
try
{
if (!this.codesByLabel.TryGetValue(FieldName, out Result))
{
Result = (uint)this.labelsByCode.Count;
if (Result == int.MaxValue)
throw Database.FlagForRepair(this.CollectionName, "Too many labels in " + this.FileName);
await this.WriteLabelLocked(FieldName);
Result++;
this.codesByLabel[FieldName] = Result;
this.labelsByCode[Result] = FieldName;
}
}
finally
{
await this.EndWrite();
}
return Result;
}
/// <summary>
/// Tries to get the code for a specific field in a collection.
/// </summary>
/// <param name="FieldName">Name of field.</param>
/// <returns>The field code, if one was found, or null otherwise.</returns>
public async Task<uint?> TryGetFieldCode(string FieldName)
{
await this.LockRead();
try
{
if (this.codesByLabel.TryGetValue(FieldName, out uint Result))
return Result;
else
return null;
}
finally
{
await this.EndRead();
}
}
/// <summary>
/// Gets an array of available labels.
/// </summary>
/// <returns>Array of labels.</returns>
public async Task<string[]> GetLabelsAsync()
{
string[] Result;
await this.LockRead();
try
{
Result = new string[this.codesByLabel.Count];
this.codesByLabel.Keys.CopyTo(Result, 0);
}
finally
{
await this.EndRead();
}
return Result;
}
/// <summary>
/// Gets the name of a field in a collection, given its code.
/// </summary>
/// <param name="FieldCode">Field code.</param>
/// <returns>Field name.</returns>
/// <exception cref="ArgumentException">If the collection or field code are not known.</exception>
public async Task<string> GetFieldName(uint FieldCode)
{
await this.LockRead();
try
{
if (this.labelsByCode.TryGetValue(FieldCode, out string Result))
return Result;
else
throw Database.FlagForRepair(this.CollectionName, "Field code unknown (" + FieldCode.ToString() + "), Collection: " + this.CollectionName);
}
finally
{
await this.EndRead();
}
}
/// <inheritdoc/>
public override void Dispose()
{
base.Dispose();
this.synchObj.Dispose();
}
}
}