-
Notifications
You must be signed in to change notification settings - Fork 0
/
Stacker.cs
413 lines (338 loc) · 15.8 KB
/
Stacker.cs
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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace Haystacks
{
public class Stacker
{
private ConfigInfo config;
public Stacker(ConfigInfo config)
{
this.config = config;
}
/// <summary>
/// Returns the total number of data files stored in the haystack group.
/// </summary>
/// <returns></returns>
public int DataFileCount()
{
int totalSize = 0;
List<string> indexFiles = Directory.GetFiles(config.StackLocation, "*.index").ToList();
foreach (string indexFile in indexFiles)
totalSize += (int)(new FileInfo(indexFile).Length);
return totalSize / 24;
}
/// <summary>
/// Returns the total number of bytes stored in the haystack group.
/// </summary>
/// <returns></returns>
public long DataSize()
{
long totalSize = 0;
List<string> stackFiles = Directory.GetFiles(config.StackLocation, "*.stack").ToList();
foreach (string stackFile in stackFiles)
totalSize += (new FileInfo(stackFile).Length);
return totalSize;
}
/// <summary>
/// Writes a byte array of data into the haystack group.
/// </summary>
/// <param name="data">The data to be written.</param>
/// <returns>Returns a NeedleInfo object with information about the data write operation.</returns>
public NeedleInfo Write(byte[] data)
{
NeedleInfo info;
using (MemoryStream stream = new MemoryStream(data))
{
info = Write(stream);
stream.Flush();
stream.Close();
}
return info;
}
/// <summary>
/// Writes a file into the haystack group.
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public NeedleInfo Write(string filename)
{
NeedleInfo info;
using (FileStream stream = File.Open(filename, FileMode.Open, FileAccess.Read))
{
info = Write(stream);
stream.Flush();
stream.Close();
}
return info;
}
/// <summary>
/// Writes a stream into the haystack group.
/// </summary>
/// <param name="stream">The stream of data to write.</param>
/// <returns>Returns a NeedleInfo object with information about the data write operation.</returns>
public NeedleInfo Write(Stream stream)
{
NeedleInfo info = new NeedleInfo();
//make sure that the input is not empty
if (stream.Length == 0)
throw new Exception("The input data cannot be empty.");
//get the lists of existing files and their sizes
List<string> indexFiles = Directory.GetFiles(config.StackLocation, "*.index").ToList();
List<string> stackFiles = Directory.GetFiles(config.StackLocation, "*.stack").ToList();
List<long> stackSizes = new List<long>();
foreach (string stackFile in stackFiles)
stackSizes.Add(new FileInfo(stackFile).Length);
//determine the best file to write the data to
int targetFileNumber = -1;
if (indexFiles.Count == 0)
targetFileNumber = 0;
else
{
//scan to find a stack with enough room
for (int counter = 0; counter < stackSizes.Count; counter++)
{
if (stackSizes[counter] + stream.Length <= config.MaximumStackSize)
{
targetFileNumber = counter;
break;
}
}
//if no files have capacity then start a new one
if (targetFileNumber == -1)
targetFileNumber = stackSizes.Count;
}
//create the index and stack files if they don't yet exist
string targetIndex = Path.Combine(config.StackLocation, targetFileNumber.ToString("0000000000") + ".index");
string targetStack = Path.Combine(config.StackLocation, targetFileNumber.ToString("0000000000") + ".stack");
if (!File.Exists(targetIndex))
File.Create(targetIndex).Close();
if (!File.Exists(targetStack))
File.Create(targetStack).Close();
//fill in the information struct
info.StackNumber = targetFileNumber;
info.NeedleNumber = (int)((new FileInfo(targetIndex).Length) / 24L);
info.StackOffset = new FileInfo(targetStack).Length;
info.NeedleSize = stream.Length;
//write the index entry
using (FileStream indexStream = File.Open(targetIndex, FileMode.Open, FileAccess.Write))
{
indexStream.Seek(0, SeekOrigin.End);
indexStream.Write(BitConverter.GetBytes(info.StackNumber), 0, 4);
indexStream.Write(BitConverter.GetBytes(info.NeedleNumber), 0, 4);
indexStream.Write(BitConverter.GetBytes(info.StackOffset), 0, 8);
indexStream.Write(BitConverter.GetBytes(info.NeedleSize), 0, 8);
indexStream.Flush();
indexStream.Close();
}
////write the stack entry
//using (FileStream stackStream = File.Open(targetStack, FileMode.Open, FileAccess.Write))
//{
// stackStream.Seek(0, SeekOrigin.End);
// stream.CopyTo(stackStream);
// stackStream.Flush();
// stackStream.Close();
//}
//write the stack entry
byte[] buffer = new byte[WinFileIO.BlockSize];
using (WinFileIO writer = new WinFileIO(buffer))
{
writer.OpenForWriting(targetStack);
writer.Position = writer.Length;
while (stream.Position < stream.Length)
{
int bytesRead = stream.Read(buffer, 0, WinFileIO.BlockSize);
writer.WriteBlocks(bytesRead);
}
}
return info;
}
/// <summary>
/// Reads a file back out of a haystack.
/// </summary>
/// <param name="stackNumber">The stack that the data was stored in.</param>
/// <param name="needleNumber">The needle number referencing the data chunk.</param>
/// <returns>A byte array containing the original data.</returns>
public byte[] Read(int stackNumber, int needleNumber)
{
byte[] output;
using (MemoryStream stream = new MemoryStream())
{
Read(stream, stackNumber, needleNumber);
stream.Flush();
stream.Close();
output = stream.ToArray();
}
return output;
}
/// <summary>
/// Reads a file back out of a haystack.
/// </summary>
/// <param name="filename">The file to write the data into.</param>
/// <param name="stackNumber">The stack that the data was stored in.</param>
/// <param name="needleNumber">The needle number referencing the data chunk.</param>
public void Read(string filename, int stackNumber, int needleNumber)
{
using (FileStream stream = File.Open(filename, FileMode.OpenOrCreate, FileAccess.Write))
{
Read(stream, stackNumber, needleNumber);
stream.Flush();
stream.Close();
}
}
/// <summary>
/// Reads a file back out of a haystack.
/// </summary>
/// <param name="stream">A stream to write the data into.</param>
/// <param name="stackNumber">The stack that the data was stored in.</param>
/// <param name="needleNumber">The needle number referencing the data chunk.</param>
public void Read(Stream stream, int stackNumber, int needleNumber)
{
//make sure that the referenced stack exists
string targetIndex = Path.Combine(config.StackLocation, stackNumber.ToString("0000000000") + ".index");
string targetStack = Path.Combine(config.StackLocation, stackNumber.ToString("0000000000") + ".stack");
if (!File.Exists(targetIndex))
throw new Exception("The index file could not be found! Ensure that the stackNumber parameter is correct.");
if (!File.Exists(targetStack))
throw new Exception("The stack file could not be found! Ensure that the stackNumber parameter is correct.");
long indexSize = new FileInfo(targetIndex).Length;
if (((long)needleNumber + 1L) * 24L > indexSize)
throw new Exception("The requested needle could not be found in this stack!");
//read metadata from the index file
long stackOffset;
long needleLength;
using (FileStream indexStream = File.Open(targetIndex, FileMode.Open, FileAccess.Read))
{
//seek to the entry for this needle
indexStream.Seek((long)needleNumber * 24L, SeekOrigin.Begin);
//skip the stack number and needle number
indexStream.Seek(8, SeekOrigin.Current);
//read the stack offset
byte[] data = new byte[8];
indexStream.Read(data, 0, 8);
stackOffset = BitConverter.ToInt64(data, 0);
//read the data length
data = new byte[8];
indexStream.Read(data, 0, 8);
needleLength = BitConverter.ToInt64(data, 0);
indexStream.Close();
}
////read data from the stack file
//using (FileStream stackStream = File.Open(targetStack, FileMode.Open, FileAccess.Read))
//{
// //seek to the beginning of the needle
// stackStream.Seek(stackOffset, SeekOrigin.Begin);
// //read all the data for the needle
// byte[] buffer = new byte[81920];
// int chunkSize = 1;
// long bytesTransferred = 0;
// while (chunkSize > 0 && bytesTransferred <= needleLength)
// {
// chunkSize = stackStream.Read(buffer, 0, buffer.Length);
// bytesTransferred += chunkSize;
// if (bytesTransferred <= needleLength)
// stream.Write(buffer, 0, chunkSize);
// else
// {
// int lastChunkSize = (int)(needleLength % buffer.Length);
// stream.Write(buffer, 0, lastChunkSize);
// }
// }
// stream.Flush();
// stackStream.Close();
//}
//read data from the stack file
byte[] buffer = new byte[WinFileIO.BlockSize];
using (WinFileIO reader = new WinFileIO(buffer))
{
reader.OpenForReading(targetStack);
//seek to the beginning of the needle
reader.Position = stackOffset;
//read all the data for the needle
int chunkSize = 1;
long bytesTransferred = 0;
while (chunkSize > 0 && bytesTransferred <= needleLength)
{
chunkSize = reader.ReadBlocks(WinFileIO.BlockSize);
bytesTransferred += chunkSize;
if (bytesTransferred <= needleLength)
stream.Write(buffer, 0, chunkSize);
else
{
int lastChunkSize = (int)(needleLength % buffer.Length);
stream.Write(buffer, 0, lastChunkSize);
}
}
}
}
/// <summary>
/// Performs a recovery after a fault, e.g. power lost while writing to a file. If no corruption occurred then the files are left untouched.
/// </summary>
public void Recover()
{
//get the lists of existing files and their sizes
List<string> indexFiles = Directory.GetFiles(config.StackLocation, "*.index").ToList();
//check if failure happened during the index write or the stack write
foreach (string indexFile in indexFiles)
{
long indexSize = new FileInfo(indexFile).Length;
if (indexSize % 24L != 0L)
{
//fault during index write
//chop off the garbage from the end of the index file
using (FileStream stream = File.Open(indexFile, FileMode.Open, FileAccess.Write))
{
stream.SetLength((indexSize / 24L) * 24L);
stream.Close();
}
}
else if (indexSize != 0)
{
//fault during stack write (or possibly no fault)
//read metadata from the index file
long stackOffset;
long needleLength;
using (FileStream stream = File.Open(indexFile, FileMode.Open, FileAccess.Read))
{
//seek to the entry for the last needle
stream.Seek(-24L, SeekOrigin.End);
//skip the stack number and needle number
stream.Seek(8, SeekOrigin.Current);
//read the stack offset
byte[] data = new byte[8];
stream.Read(data, 0, 8);
stackOffset = BitConverter.ToInt64(data, 0);
//read the data length
data = new byte[8];
stream.Read(data, 0, 8);
needleLength = BitConverter.ToInt64(data, 0);
stream.Close();
}
//get the path of the stack file
string stackFile = Path.GetFileName(indexFile).ToLower().Replace(".index", ".stack");
stackFile = Path.Combine(config.StackLocation, stackFile);
long stackSize = new FileInfo(stackFile).Length;
//did I/O fail during the needle write?
if (stackSize != stackOffset + needleLength)
{
//chop off the last entry of the index file
using (FileStream stream = File.Open(indexFile, FileMode.Open, FileAccess.Write))
{
stream.SetLength(indexSize - 24L);
stream.Close();
}
//chop off the garbage from the end of the stack file
using (FileStream stream = File.Open(stackFile, FileMode.Open, FileAccess.Write))
{
stream.SetLength(stackOffset);
stream.Close();
}
}
}
}
}
}
}