DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
StoreBase.cs
Go to the documentation of this file.
1 //
2 // Copyright 2010-2015 Deveel
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 using System;
18 using System.Collections.Generic;
19 using System.IO;
20 using System.Text;
21 
22 using Deveel.Data.Util;
23 
24 namespace Deveel.Data.Store {
25  public abstract class StoreBase : IStore {
26  private long[] freeBinList;
27  private long totalAllocatedSpace;
28 
29  private readonly byte[] binArea = new byte[128 * 8];
30 
31  private static readonly int[] BinSizes =
32  {
33  32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480,
34  512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928,
35  960, 992, 1024, 1056, 1088, 1120, 1152, 1184, 1216, 1248, 1280, 1312,
36  1344, 1376, 1408, 1440, 1472, 1504, 1536, 1568, 1600, 1632, 1664, 1696,
37  1728, 1760, 1792, 1824, 1856, 1888, 1920, 1952, 1984, 2016, 2048, 2080,
38  2144, 2208, 2272, 2336, 2400, 2464, 2528, 2592, 2656, 2720, 2784, 2848,
39  2912, 2976, 3040, 3104, 3168, 3232, 3296, 3360, 3424, 3488, 3552, 3616,
40  3680, 3744, 3808, 3872, 3936, 4000, 4064, 4128, 4384, 4640, 4896, 5152,
41  5408, 5664, 5920, 6176, 6432, 6688, 6944, 7200, 7456, 7712, 7968, 8224,
42  10272, 12320, 14368, 16416, 18464, 20512, 22560, 24608, 57376, 90144,
43  122912, 155680, 1204256, 2252832
44  };
45 
46  private readonly static int BinSizeEntries = BinSizes.Length;
47  private readonly static int MaxBinSize = BinSizes[BinSizeEntries - 1];
48 
49  private const long ActiveFlag = Int64.MaxValue;
50  private const long DeletedFlag = Int64.MinValue;
51 
52  private const long FixedAreaOffset = 128;
53 
54  // The offset into the file that the data areas start.
55  private const long DataAreaOffset = 256 + 1024 + 32;
56 
57  private const long BinAreaOffset = 256;
58 
59  private const int Magic = 0x0AEAE91;
60 
61  protected StoreBase(bool isReadOnly) {
62  freeBinList = new long[BinSizeEntries + 1];
63  for (int i = 0; i < BinSizeEntries + 1; ++i) {
64  freeBinList[i] = -1L;
65  }
66 
67  WildernessOffset = -1;
68 
69  IsReadOnly = isReadOnly;
70  }
71 
73  Dispose(false);
74  }
75 
76  private long WildernessOffset { get; set; }
77 
78  public bool IsReadOnly { get; private set; }
79 
80  protected abstract long DataAreaEndOffset { get; }
81 
82  private void CheckOffset(long offset) {
83  if (offset < DataAreaOffset || offset >= DataAreaEndOffset) {
84  throw new IOException(String.Format("The offset is out of range ({0} > {1} > {2})", DataAreaOffset, offset,
85  DataAreaEndOffset));
86  }
87  }
88 
89  private static int MinimumBinSizeIndex(long size) {
90  int i = Array.BinarySearch(BinSizes, (int)size);
91  if (i < 0) {
92  i = -(i + 1);
93  }
94  return i;
95  }
96 
97  private void ReadBins() {
98  Read(BinAreaOffset, binArea, 0, 128 * 8);
99  using (var bin = new MemoryStream(binArea)) {
100  using (BinaryReader input = new BinaryReader(bin)) {
101  for (int i = 0; i < 128; ++i) {
102  freeBinList[i] = input.ReadInt64();
103  }
104  }
105  }
106  }
107 
108  private void WriteAllBins() {
109  int p = 0;
110  for (int i = 0; i < 128; ++i, p += 8) {
111  long val = freeBinList[i];
112  ByteBuffer.WriteInt8(val, binArea, p);
113  }
114 
115  Write(BinAreaOffset, binArea, 0, 128 * 8);
116  }
117 
118  private void WriteBinIndex(int index) {
119  int p = index * 8;
120  long val = freeBinList[index];
121  ByteBuffer.WriteInt8(val, binArea, p);
122  Write(BinAreaOffset + p, binArea, p, 8);
123  }
124 
125  private void AddToBinChain(long pointer, long size) {
126  CheckOffset(pointer);
127 
128  // What bin would this area fit into?
129  int binChainIndex = MinimumBinSizeIndex(size);
130  var headerInfo = new long[2];
131 
132  long curOffset = freeBinList[binChainIndex];
133 
134  if (curOffset == -1) {
135  // If the bin chain has no elements,
136  headerInfo[0] = (size | DeletedFlag);
137  headerInfo[1] = -1;
138 
139  ReboundArea(pointer, headerInfo, true);
140  freeBinList[binChainIndex] = pointer;
141 
142  WriteBinIndex(binChainIndex);
143  } else {
144  bool inserted = false;
145  long lastOffset = -1;
146  int searches = 0;
147  while (curOffset != -1 && inserted == false) {
148  // Get the current offset
149  ReadAreaHeader(curOffset, headerInfo);
150 
151  long header = headerInfo[0];
152  long next = headerInfo[1];
153 
154  // Assert - the header must have deleted flag
155  if ((header & DeletedFlag) == 0)
156  throw new IOException("Area not marked as deleted.");
157 
158  long areaSize = header ^ DeletedFlag;
159  if (areaSize >= size || searches >= 12) {
160  // Insert if the area size is >= than the size we are adding.
161  // Set the previous header to point to this
162  long previous = lastOffset;
163 
164  // Set up the deleted area
165  headerInfo[0] = (size | DeletedFlag);
166  headerInfo[1] = curOffset;
167 
168  ReboundArea(pointer, headerInfo, true);
169 
170  if (lastOffset != -1) {
171  // Set the previous input the chain to point to the deleted area
172  ReadAreaHeader(previous, headerInfo);
173 
174  headerInfo[1] = pointer;
175  ReboundArea(previous, headerInfo, false);
176  } else {
177  // Otherwise set the head bin item
178  freeBinList[binChainIndex] = pointer;
179  WriteBinIndex(binChainIndex);
180  }
181 
182  inserted = true;
183  }
184 
185  lastOffset = curOffset;
186  curOffset = next;
187  ++searches;
188  }
189 
190  // If we reach the end and we haven't inserted,
191  if (!inserted) {
192  // Set the new deleted area.
193  headerInfo[0] = (size | DeletedFlag);
194  headerInfo[1] = -1;
195  ReboundArea(pointer, headerInfo, true);
196 
197  // Set the previous entry to this
198  ReadAreaHeader(lastOffset, headerInfo);
199  headerInfo[1] = pointer;
200  ReboundArea(lastOffset, headerInfo, false);
201  }
202  }
203  }
204 
205  private void RemoveFromBinChain(long offset, long size) {
206  // What bin index should we be looking input?
207  int binChainIndex = MinimumBinSizeIndex(size);
208 
209  var prevOffset = -1L;
210  var curOffset = freeBinList[binChainIndex];
211 
212  // Search this bin for the offset
213  // NOTE: This is an iterative search through the bin chain
214  while (offset != curOffset) {
215  if (curOffset == -1)
216  throw new IOException("Area not found input bin chain.");
217 
218  // Move to the next input the chain
219  var headerInfo = new long[2];
220  ReadAreaHeader(curOffset, headerInfo);
221 
222  prevOffset = curOffset;
223  curOffset = headerInfo[1];
224  }
225 
226  // Found the offset, so remove it,
227  if (prevOffset == -1) {
228  var headerInfo = new long[2];
229 
230  ReadAreaHeader(offset, headerInfo);
231  freeBinList[binChainIndex] = headerInfo[1];
232  WriteBinIndex(binChainIndex);
233  } else {
234  var headerInfo = new long[2];
235  var headerInfo2 = new long[2];
236 
237  ReadAreaHeader(prevOffset, headerInfo2);
238  ReadAreaHeader(offset, headerInfo);
239  headerInfo2[1] = headerInfo[1];
240  ReboundArea(prevOffset, headerInfo2, false);
241  }
242 
243  }
244 
245  private void Free(long pointer) {
246  // Get the area header
247  var headerInfo = new long[2];
248  ReadAreaHeader(pointer, headerInfo);
249 
250  if ((headerInfo[0] & DeletedFlag) != 0)
251  throw new IOException("Area already marked as unallocated.");
252 
253  // If (pointer + size) reaches the end of the header area, set this as the
254  // wilderness.
255  bool setAsWilderness = ((pointer + headerInfo[0]) >= DataAreaEndOffset);
256 
257  var rOffset = pointer;
258  var freeingAreaSize = headerInfo[0];
259  var rSize = freeingAreaSize;
260 
261  // Can this area coalesce?
262  var headerInfo2 = new long[2];
263  var leftPointer = GetPreviousAreaHeader(pointer, headerInfo2);
264  var coalesc = false;
265 
266  if ((headerInfo2[0] & DeletedFlag) != 0) {
267  // Yes, we can coalesce left
268  long areaSize = (headerInfo2[0] & ActiveFlag);
269 
270  rOffset = leftPointer;
271  rSize = rSize + areaSize;
272 
273  // Remove left area from the bin
274  RemoveFromBinChain(leftPointer, areaSize);
275  coalesc = true;
276  }
277 
278  if (!setAsWilderness) {
279  long rightPointer = GetNextAreaHeader(pointer, headerInfo2);
280  if ((headerInfo2[0] & DeletedFlag) != 0) {
281  // Yes, we can coalesce right
282  long areaSize = (headerInfo2[0] & ActiveFlag);
283 
284  rSize = rSize + areaSize;
285 
286  // Remove right from the bin
287  RemoveFromBinChain(rightPointer, areaSize);
288  setAsWilderness = (rightPointer == WildernessOffset);
289  coalesc = true;
290  }
291  }
292 
293  // If we are coalescing parent areas
294  if (coalesc)
295  CoalesceArea(rOffset, rSize);
296 
297  // Add this new area to the bin chain,
298  AddToBinChain(rOffset, rSize);
299 
300  // Do we set this as the wilderness?
301  if (setAsWilderness)
302  WildernessOffset = rOffset;
303 
304  totalAllocatedSpace -= freeingAreaSize;
305  }
306 
307 
308  private long GetPreviousAreaHeader(long offset, long[] header) {
309  // If the offset is the start of the file area
310  if (offset == DataAreaOffset) {
311  // Return a 0 sized block
312  header[0] = 0;
313  return -1;
314  }
315 
316  Read(offset - 8, headerBuf, 0, 8);
317  long sz = ByteBuffer.ReadInt8(headerBuf, 0);
318  sz = sz & ActiveFlag;
319  long previousPointer = offset - sz;
320  Read(previousPointer, headerBuf, 0, 8);
321  header[0] = ByteBuffer.ReadInt8(headerBuf, 0);
322  return previousPointer;
323  }
324 
325  private long GetNextAreaHeader(long offset, long[] header) {
326  Read(offset, headerBuf, 0, 8);
327  long sz = ByteBuffer.ReadInt8(headerBuf, 0);
328  sz = sz & ActiveFlag;
329  long nextOffset = offset + sz;
330 
331  if (nextOffset >= DataAreaEndOffset) {
332  // Return a 0 sized block
333  header[0] = 0;
334  return -1;
335  }
336 
337  Read(nextOffset, headerBuf, 0, 8);
338  header[0] = ByteBuffer.ReadInt8(headerBuf, 0);
339  return nextOffset;
340  }
341 
342  protected void ReadAreaHeader(long offset, long[] header) {
343  Read(offset, headerBuf, 0, 16);
344  header[0] = ByteBuffer.ReadInt8(headerBuf, 0);
345  header[1] = ByteBuffer.ReadInt8(headerBuf, 8);
346  }
347 
348  private readonly byte[] headerBuf = new byte[16];
349 
350  private void ReboundArea(long offset, long[] header, bool writeHeaders) {
351  if (writeHeaders) {
352  ByteBuffer.WriteInt8(header[0], headerBuf, 0);
353  ByteBuffer.WriteInt8(header[1], headerBuf, 8);
354  Write(offset, headerBuf, 0, 16);
355  } else {
356  ByteBuffer.WriteInt8(header[1], headerBuf, 8);
357  Write(offset + 8, headerBuf, 8, 8);
358  }
359  }
360 
361  private void CoalesceArea(long offset, long size) {
362  ByteBuffer.WriteInt8(size, headerBuf, 0);
363 
364  // ISSUE: Boundary alteration is a moment when corruption could occur.
365  // There are two seeks and writes here and when we are setting the
366  // end points, there is a risk of failure.
367 
368  Write(offset, headerBuf, 0, 8);
369  Write((offset + size) - 8, headerBuf, 0, 8);
370  }
371 
372  private void CropArea(long offset, long allocatedSize) {
373  // Get the header info
374  var headerInfo = new long[2];
375  ReadAreaHeader(offset, headerInfo);
376 
377  var header = headerInfo[0];
378 
379  var freeAreaSize = header;
380  var sizeDifference = freeAreaSize - allocatedSize;
381  var isWilderness = (offset == WildernessOffset);
382 
383  // If the difference is greater than 512 bytes, add the excess space to
384  // a free bin.
385 
386  if ((isWilderness && sizeDifference >= 32) || sizeDifference >= 512) {
387  // Split the area into two areas.
388  SplitArea(offset, allocatedSize);
389 
390  long leftOverPointer = offset + allocatedSize;
391  // Add this area to the bin chain
392  AddToBinChain(leftOverPointer, sizeDifference);
393 
394  // If offset is the wilderness area, set this as the new wilderness
395  if (isWilderness ||
396  (leftOverPointer + sizeDifference) >= DataAreaEndOffset) {
397  WildernessOffset = leftOverPointer;
398  }
399 
400  } else {
401  // If offset is the wilderness area, set wilderness to -1
402  if (isWilderness) {
403  WildernessOffset = -1;
404  }
405  }
406  }
407 
408  private long Alloc(long size) {
409  if (size < 0)
410  throw new IOException("Negative size allocation");
411 
412  // Add 16 bytes for headers
413  size = size + 16;
414 
415  // If size < 32, make size = 32
416  if (size < 32)
417  size = 32;
418 
419  // Round all sizes up to the nearest 8
420  long d = size & 0x07L;
421  if (d != 0)
422  size = size + (8 - d);
423 
424  long realAllocSize = size;
425 
426  // Search the free bin list for the first bin that matches the given size.
427  int binChainIndex;
428  if (size > MaxBinSize) {
429  binChainIndex = BinSizeEntries;
430  } else {
431  int i = MinimumBinSizeIndex(size);
432  binChainIndex = i;
433  }
434 
435  // Search the bins until we find the first area that is the nearest fit to
436  // the size requested.
437  int foundBinIndex = -1;
438  long prevOffset = -1;
439  bool first = true;
440  for (int i = binChainIndex;
441  i < BinSizeEntries + 1 && foundBinIndex == -1;
442  ++i) {
443  long curOffset = freeBinList[i];
444  if (curOffset != -1) {
445  if (!first) {
446  // Pick this..
447  foundBinIndex = i;
448  prevOffset = -1;
449  } else {
450  // Search this bin for the first that's big enough.
451  // We only search the first 12 entries input the bin before giving up.
452 
453  long lastOffset = -1;
454  int searches = 0;
455  while (curOffset != -1 &&
456  foundBinIndex == -1 &&
457  searches < 12) {
458 
459  var headerInfo = new long[2];
460  ReadAreaHeader(curOffset, headerInfo);
461 
462  long areaSize = (headerInfo[0] & ActiveFlag);
463 
464  // Is this area is greater or equal than the required size
465  // and is not the wilderness area, pick it.
466  if (curOffset != WildernessOffset && areaSize >= size) {
467  foundBinIndex = i;
468  prevOffset = lastOffset;
469  }
470 
471  // Go to next input chain.
472  lastOffset = curOffset;
473  curOffset = headerInfo[1];
474  ++searches;
475  }
476  }
477 
478  }
479 
480  first = false;
481  }
482 
483  // If no area can be recycled,
484  if (foundBinIndex == -1) {
485  // Allocate a new area of the given size.
486  // If there is a wilderness, grow the wilderness area to the new size,
487  long workingOffset;
488  long sizeToGrow;
489  long currentAreaSize;
490  if (WildernessOffset != -1) {
491  workingOffset = WildernessOffset;
492 
493  var headerInfo = new long[2];
494  ReadAreaHeader(WildernessOffset, headerInfo);
495 
496  long wildernessSize = (headerInfo[0] & ActiveFlag);
497 
498  // Remove this from the bins
499  RemoveFromBinChain(workingOffset, wildernessSize);
500 
501  // For safety, we set wilderness_pointer to -1
502  WildernessOffset = -1;
503  sizeToGrow = size - wildernessSize;
504  currentAreaSize = wildernessSize;
505  } else {
506  // wilderness_pointer == -1 so add to the end of the data area.
507  workingOffset = DataAreaEndOffset;
508  sizeToGrow = size;
509  currentAreaSize = 0;
510  }
511 
512  long expandedSize = 0;
513  if (sizeToGrow > 0) {
514  // Expand the data area to the new size.
515  expandedSize = ExpandDataArea(sizeToGrow);
516  }
517 
518  // Coalesce the new area to the given size
519  CoalesceArea(workingOffset, currentAreaSize + expandedSize);
520 
521  // crop the area
522  CropArea(workingOffset, size);
523 
524  // Add to the total allocated space
525  totalAllocatedSpace += realAllocSize;
526 
527  return workingOffset;
528  } else {
529  // An area is taken from the bins,
530  long freeAreaOffset;
531  var headerInfo = new long[2];
532 
533  // Remove this area from the bin chain and possibly add any excess space
534  // left over to a new bin.
535  if (prevOffset == -1) {
536  freeAreaOffset = freeBinList[foundBinIndex];
537  ReadAreaHeader(freeAreaOffset, headerInfo);
538  freeBinList[foundBinIndex] = headerInfo[1];
539  WriteBinIndex(foundBinIndex);
540  } else {
541  var headerInfo2 = new long[2];
542  ReadAreaHeader(prevOffset, headerInfo2);
543  freeAreaOffset = headerInfo2[1];
544  ReadAreaHeader(freeAreaOffset, headerInfo);
545  headerInfo2[1] = headerInfo[1];
546  ReboundArea(prevOffset, headerInfo2, false);
547  }
548 
549  // Reset the header of the recycled area.
550  headerInfo[0] = (headerInfo[0] & ActiveFlag);
551  ReboundArea(freeAreaOffset, headerInfo, true);
552 
553  // Crop the area to the given size.
554  CropArea(freeAreaOffset, size);
555 
556  // Add to the total allocated space
557  totalAllocatedSpace += realAllocSize;
558 
559  return freeAreaOffset;
560  }
561  }
562 
563 
564  private long ExpandDataArea(long minSize) {
565  long endOfDataArea = DataAreaEndOffset;
566 
567  // Round all sizes up to the nearest 8
568  // We grow only by a small amount if the area is small, and a large amount
569  // if the area is large.
570  long overGrow = endOfDataArea / 64;
571  long d = (overGrow & 0x07L);
572  if (d != 0)
573  overGrow = overGrow + (8 - d);
574 
575  overGrow = System.Math.Min(overGrow, 262144L);
576  if (overGrow < 1024)
577  overGrow = 1024;
578 
579  long growBy = minSize + overGrow;
580  long newFileLength = endOfDataArea + growBy;
581  SetDataAreaSize(newFileLength);
582  return growBy;
583  }
584 
585  protected void SplitArea(long offset, long newBoundary) {
586  // Split the area pointed to by the offset.
587  Read(offset, headerBuf, 0, 8);
588  long curSize = ByteBuffer.ReadInt8(headerBuf, 0) & ActiveFlag;
589  long leftSize = newBoundary;
590  long rightSize = curSize - newBoundary;
591 
592  if (rightSize < 0)
593  throw new IOException("Could not split the area.");
594 
595  ByteBuffer.WriteInt8(leftSize, headerBuf, 0);
596  ByteBuffer.WriteInt8(rightSize, headerBuf, 8);
597 
598  // ISSUE: Boundary alteration is a moment when corruption could occur.
599  // There are three seeks and writes here and when we are setting the
600  // end points, there is a risk of failure.
601 
602  // First set the boundary
603  Write((offset + newBoundary) - 8, headerBuf, 0, 16);
604  // Now set the end points
605  Write(offset, headerBuf, 0, 8);
606  Write((offset + curSize) - 8, headerBuf, 8, 8);
607  }
608 
609  private static bool IsValidBoundarySize(long size) {
610  const long maxAreaSize = (long)Int32.MaxValue * 200;
611  size = size & ActiveFlag;
612  return ((size < maxAreaSize) && (size >= 24) && ((size & 0x07) == 0));
613  }
614 
615  private void Init() {
616  lock (this) {
617  SetDataAreaSize(DataAreaOffset);
618 
619  using (var stream = new MemoryStream((int) BinAreaOffset)) {
620  using (BinaryWriter writer = new BinaryWriter(stream, Encoding.Unicode)) {
621 
622  // The file MAGIC
623  writer.Write(Magic); // 0
624 
625  // The file version
626  writer.Write(1); // 4
627 
628  // The number of areas (chunks) input the file (currently unused)
629  writer.Write(-1L); // 8
630 
631  // File open/close status byte
632  writer.Write((byte) 0); // 16
633 
634  writer.Flush();
635 
636  byte[] buffer = new byte[(int) DataAreaOffset];
637  byte[] temp = stream.ToArray();
638  Array.Copy(temp, 0, buffer, 0, temp.Length);
639 
640  for (int i = (int) BinAreaOffset; i < (int) DataAreaOffset; ++i) {
641  buffer[i] = 255;
642  }
643 
644  Write(0, buffer, 0, buffer.Length);
645  }
646  }
647  }
648  }
649 
650 
651  protected abstract void SetDataAreaSize(long length);
652 
653  public bool Open() {
654  lock (this) {
655  OpenStore(IsReadOnly);
656 
657  // If it's small, initialize to empty
658  if (DataAreaEndOffset < DataAreaOffset)
659  Init();
660 
661  byte[] readBuf = new byte[(int) BinAreaOffset];
662  Read(0, readBuf, 0, readBuf.Length);
663 
664  using (var stream = new MemoryStream(readBuf)) {
665  using (var reader = new BinaryReader(stream)) {
666 
667  int magic = reader.ReadInt32();
668  if (magic != Magic)
669  throw new IOException("Format invalid: Magic value is not as expected.");
670 
671  int version = reader.ReadInt32();
672  if (version != 1)
673  throw new IOException("Format invalid: unrecognized version.");
674 
675  reader.ReadInt64(); // ignore
676  byte status = reader.ReadByte();
677  ClosedClean = true;
678 
679  if (status == 1) {
680  // This means the store wasn't closed cleanly.
681  ClosedClean = false;
682  }
683  }
684  }
685 
686  // Read the bins
687  ReadBins();
688 
689  // Mark the file as open
690  if (!IsReadOnly)
691  Write(16, 1);
692 
693  long fileLength = DataAreaEndOffset;
694  if (fileLength <= 8) {
695  throw new IOException("Format invalid: File size is too small.");
696  }
697 
698  // Set the wilderness offset.
699  if (fileLength == DataAreaOffset) {
700  WildernessOffset = -1;
701  } else {
702  Read(fileLength - 8, readBuf, 0, 8);
703  long lastBoundary = ByteBuffer.ReadInt8(readBuf, 0);
704  long lastAreaPointer = fileLength - lastBoundary;
705 
706  if (lastAreaPointer < DataAreaOffset)
707  throw new IOException("File corrupt: last area offset is before data part of file.");
708 
709  if (lastAreaPointer > fileLength - 8)
710  throw new IOException("File corrupt: last_area_pointer at the end of the file.");
711 
712  Read(lastAreaPointer, readBuf, 0, 8);
713 
714  long lastAreaHeader = ByteBuffer.ReadInt8(readBuf, 0);
715 
716  // If this is a freed block, then set this are the wilderness offset.
717  if ((lastAreaHeader & DeletedFlag) != 0) {
718  WildernessOffset = lastAreaPointer;
719  } else {
720  WildernessOffset = -1;
721  }
722  }
723 
724  return ClosedClean;
725  }
726  }
727 
728  public void Close() {
729  lock (this) {
730  // Mark the file as closed
731  if (!IsReadOnly)
732  Write(16, 0);
733 
734  CloseStore();
735  }
736  }
737 
738  public void Dispose() {
739  Dispose(true);
740  GC.SuppressFinalize(this);
741  }
742 
743  private bool disposed;
744 
745  protected virtual void Dispose(bool disposing) {
746  if (!disposed) {
747  if (disposing) {
748  Close();
749  }
750 
751  disposed = true;
752  }
753  }
754 
755  public IArea CreateArea(long size) {
756  lock (this) {
757  long pointer = Alloc(size);
758  return new StoreArea(this, pointer, pointer + 8, false, size);
759  }
760  }
761 
762  public void DeleteArea(long id) {
763  lock (this) {
764  Free(id);
765  }
766  }
767 
768  public IArea GetArea(long id, bool readOnly) {
769  // If this is the fixed area
770  if (id == -1)
771  return new StoreArea(this, id, FixedAreaOffset, readOnly, 64);
772 
773  // Otherwise must be a regular area
774  return new StoreArea(this, id, id, readOnly);
775  }
776 
777  public abstract void Lock();
778 
779  public abstract void Unlock();
780 
781  //public abstract void CheckPoint();
782 
783  public bool ClosedClean { get; private set; }
784 
785  public IEnumerable<long> GetAllAreas() {
786  var list = new List<long>();
787 
788  long endOfDataArea = DataAreaEndOffset;
789 
790  long[] header = new long[2];
791  // The first header
792  long offset = DataAreaOffset;
793  while (offset < endOfDataArea) {
794  ReadAreaHeader(offset, header);
795 
796  long areaSize = (header[0] & ActiveFlag);
797  if ((header[0] & DeletedFlag) == 0)
798  list.Add(offset);
799 
800  offset += areaSize;
801  }
802 
803  return list;
804  }
805 
806  internal IEnumerable<long> FindAllocatedAreasNotIn(List<long> usedAreas) {
807  // Sort the list
808  var list = new List<long>(usedAreas);
809  list.Sort();
810 
811  // The list of leaked areas
812  var leakedAreas = new List<long>();
813 
814  int listIndex = 0;
815 
816  // What area are we looking for?
817  long lookingFor = Int64.MaxValue;
818  if (listIndex < list.Count) {
819  lookingFor = list[listIndex];
820  ++listIndex;
821  }
822 
823  long endOfDataArea = DataAreaEndOffset;
824  long[] header = new long[2];
825 
826  long offset = DataAreaOffset;
827  while (offset < endOfDataArea) {
828  ReadAreaHeader(offset, header);
829 
830  long areaSize = (header[0] & ActiveFlag);
831  bool areaFree = (header[0] & DeletedFlag) != 0;
832 
833  if (offset == lookingFor) {
834  if (areaFree)
835  throw new IOException("Area is not allocated!");
836 
837  // Update the 'looking_for' offset
838  if (listIndex < list.Count) {
839  lookingFor = (long)list[listIndex];
840  ++listIndex;
841  } else {
842  lookingFor = Int64.MaxValue;
843  }
844  } else if (offset > lookingFor) {
845  throw new IOException("IArea (offset = " + lookingFor + ") wasn't found input store!");
846  } else {
847  // An area that isn't input the list
848  if (!areaFree) {
849  // This is a leaked area.
850  // It isn't free and it isn't input the list
851  leakedAreas.Add(offset);
852  }
853  }
854 
855  offset += areaSize;
856  }
857 
858  return leakedAreas.ToArray();
859  }
860 
861  protected abstract void OpenStore(bool readOnly);
862 
863  protected abstract void CloseStore();
864 
865  protected int ReadByte(long offset) {
866  var buffer = new byte[1];
867  var count = Read(offset, buffer, 0, 1);
868  if (count == 0)
869  return -1;
870 
871  return buffer[0];
872  }
873 
874  protected abstract int Read(long offset, byte[] buffer, int index, int length);
875 
876  protected void Write(long offset, byte value) {
877  Write(offset, new []{value}, 0, 1);
878  }
879 
880  protected abstract void Write(long offset, byte[] buffer, int index, int length);
881 
882  #region StoreArea
883 
887  class StoreArea : IArea {
888  private byte[] buffer = new byte[BufferSize];
889  private long position;
890 
891  private const int BufferSize = 8;
892 
893  public StoreArea(StoreBase store, long id, long offset, bool readOnly) {
894  Store = store;
895  Id = id;
896  IsReadOnly = readOnly;
897 
898  store.CheckOffset(offset);
899 
900  store.Read(offset, buffer, 0, 8);
901  long v = ByteBuffer.ReadInt8(buffer, 0);
902  if ((v & DeletedFlag) != 0)
903  throw new IOException("Store being constructed on deleted area.");
904 
905  long maxSize = v - 16;
906  StartOffset = offset + 8;
907  position = StartOffset;
908  EndOffset = StartOffset + maxSize;
909  }
910 
911  public StoreArea(StoreBase store, long id, long offset, bool readOnly, long fixedSize) {
912  Store = store;
913  Id = id;
914  IsReadOnly = readOnly;
915 
916  // Check the offset is valid
917  if (offset != FixedAreaOffset) {
918  store.CheckOffset(offset);
919  }
920 
921  StartOffset = offset;
922  position = StartOffset;
923  EndOffset = StartOffset + fixedSize;
924  }
925 
926  public long Id { get; private set; }
927 
928  private long StartOffset { get; set; }
929 
930  private long EndOffset { get; set; }
931 
932  private StoreBase Store { get; set; }
933 
934  public virtual bool IsReadOnly { get; private set; }
935 
936  public long Position {
937  get { return position - StartOffset; }
938  set {
939  long actPosition = StartOffset + value;
940  if (actPosition < 0 || actPosition >= EndOffset)
941  throw new IOException("Moved position out of the area bounds.");
942 
943  position = actPosition;
944  }
945  }
946 
947  public int Length {
948  get { return (int)(EndOffset - StartOffset); }
949  }
950 
951  protected long CheckAreaOffset(int diff) {
952  long newPos = position + diff;
953  if (newPos > EndOffset)
954  throw new IOException("Trying to access a position out of area bounds.");
955 
956  long oldPos = position;
957  position = newPos;
958  return oldPos;
959  }
960 
961  public void CopyTo(IArea destArea, int size) {
962  // NOTE: Assuming 'destination' is a StoreArea, the temporary buffer
963  // could be optimized away to a direct Array.Ccopy. However, this
964  // function would need to be written as a lower level IO function.
965  const int bufferSize = 2048;
966  byte[] buf = new byte[bufferSize];
967  int toCopy = System.Math.Min(size, bufferSize);
968 
969  while (toCopy > 0) {
970  var read = Read(buf, 0, toCopy);
971  if (read == 0)
972  break;
973 
974  destArea.Write(buf, 0, read);
975  size -= toCopy;
976  toCopy = System.Math.Min(size, bufferSize);
977  }
978  }
979 
980  public int Read(byte[] buffer, int offset, int length) {
981  return Store.Read(CheckAreaOffset(length), buffer, offset, length);
982  }
983 
984  public void Write(byte[] buffer, int offset, int length) {
985  if (IsReadOnly)
986  throw new IOException("The area is read-only access.");
987 
988  Store.Write(CheckAreaOffset(length), buffer, offset, length);
989  }
990 
991  public void Flush() {
992  }
993  }
994 
995  #endregion
996  }
997 }
long Alloc(long size)
Definition: StoreBase.cs:408
void CropArea(long offset, long allocatedSize)
Definition: StoreBase.cs:372
void Free(long pointer)
Definition: StoreBase.cs:245
static int MinimumBinSizeIndex(long size)
Definition: StoreBase.cs:89
void ReboundArea(long offset, long[] header, bool writeHeaders)
Definition: StoreBase.cs:350
void SplitArea(long offset, long newBoundary)
Definition: StoreBase.cs:585
An IArea that is backed by a StoreBase.
Definition: StoreBase.cs:887
void CheckOffset(long offset)
Definition: StoreBase.cs:82
long GetPreviousAreaHeader(long offset, long[] header)
Definition: StoreBase.cs:308
void Write(byte[] buffer, int offset, int length)
void CopyTo(IArea destArea, int size)
Copies the given amount of bytes from the current position of the this area to another one...
Definition: StoreBase.cs:961
virtual void Dispose(bool disposing)
Definition: StoreBase.cs:745
IEnumerable< long > FindAllocatedAreasNotIn(List< long > usedAreas)
Definition: StoreBase.cs:806
void Write(long offset, byte value)
Definition: StoreBase.cs:876
long GetNextAreaHeader(long offset, long[] header)
Definition: StoreBase.cs:325
void CoalesceArea(long offset, long size)
Definition: StoreBase.cs:361
abstract int Read(long offset, byte[] buffer, int index, int length)
void RemoveFromBinChain(long offset, long size)
Definition: StoreBase.cs:205
StoreArea(StoreBase store, long id, long offset, bool readOnly)
Definition: StoreBase.cs:893
void WriteBinIndex(int index)
Definition: StoreBase.cs:118
StoreBase(bool isReadOnly)
Definition: StoreBase.cs:61
IArea CreateArea(long size)
Allocates a block of memory in the store of the specified size and returns an IArea object that can b...
Definition: StoreBase.cs:755
int Read(byte[] buffer, int offset, int length)
Reads an array of bytes from the underlying IArea and advances the position by length ...
Definition: StoreBase.cs:980
An interface for access the contents of an area of a store.
Definition: IArea.cs:23
int ReadByte(long offset)
Definition: StoreBase.cs:865
A wrapper for an array of byte.
Definition: ByteBuffer.cs:27
StoreArea(StoreBase store, long id, long offset, bool readOnly, long fixedSize)
Definition: StoreBase.cs:911
void DeleteArea(long id)
Deletes an area that was previously allocated by the CreateArea method by the area id...
Definition: StoreBase.cs:762
static long ReadInt8(byte[] arr, int offset)
Definition: ByteBuffer.cs:234
long ExpandDataArea(long minSize)
Definition: StoreBase.cs:564
void ReadAreaHeader(long offset, long[] header)
Definition: StoreBase.cs:342
IArea GetArea(long id, bool readOnly)
Returns an object that allows for the contents of an area (represented by the id parameter) to be Re...
Definition: StoreBase.cs:768
void AddToBinChain(long pointer, long size)
Definition: StoreBase.cs:125
IEnumerable< long > GetAllAreas()
Returns a complete list of pointers to all areas in the Store as long objects sorted from lowest poin...
Definition: StoreBase.cs:785
A store is a resource where areas can be allocated and freed to store information (a memory allocator...
Definition: IStore.cs:56
static bool IsValidBoundarySize(long size)
Definition: StoreBase.cs:609
static void WriteInt8(long value, byte[] arr, int offset)
Definition: ByteBuffer.cs:252
void Write(byte[] buffer, int offset, int length)
Definition: StoreBase.cs:984