DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
ScatteringFileStoreData.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 namespace Deveel.Data.Store {
23  public sealed class ScatteringFileStoreData : IStoreData {
24  private readonly object objectLock = new object();
25  private List<FileStoreData> fileSlices;
26  private long trueFileLength;
27 
28  public ScatteringFileStoreData(IFileSystem fileSystem, string basePath, string fileName, string fileExtention, int maxFileSlice) {
29  if (fileSystem == null)
30  throw new ArgumentNullException("fileSystem");
31 
32  FileSystem = fileSystem;
33  MaxFileSlice = maxFileSlice;
34  FileExtention = fileExtention;
35  FileName = fileName;
36  BasePath = basePath;
37  fileSlices = new List<FileStoreData>();
38  }
39 
41  Dispose(false);
42  }
43 
44  public string BasePath { get; private set; }
45 
46  public string FileName { get; private set; }
47 
48  public string FileExtention { get; private set; }
49 
50  public int MaxFileSlice { get; private set; }
51 
52  public IFileSystem FileSystem { get; private set; }
53 
54  public int FileCount {
55  get {
56  int i = 0;
57  string f = SliceFileName(i);
58  while (FileSystem.FileExists(f)) {
59  ++i;
60  f = SliceFileName(i);
61  }
62  return i;
63  }
64  }
65 
66  public bool IsOpen { get; private set; }
67 
68  private string SliceFileName(int i) {
69  if (i == 0)
70  return FileSystem.CombinePath(BasePath, String.Format("{0}.{1}", FileName, FileExtention));
71 
72  var fn = new StringBuilder();
73  fn.Append(FileName);
74  fn.Append(".");
75  if (i < 10) {
76  fn.Append("00");
77  } else if (i < 100) {
78  fn.Append("0");
79  }
80  fn.Append(i);
81  return Path.Combine(BasePath, fn.ToString());
82  }
83 
84  private long DiscoverSize() {
85  long runningTotal = 0;
86 
87  lock (objectLock) {
88  // Does the file exist?
89  int i = 0;
90  string f = SliceFileName(i);
91  while (FileSystem.FileExists(f)) {
92  var fileLength = FileSystem.GetFileSize(f);
93 
94  runningTotal += fileLength;
95 
96  ++i;
97  f = SliceFileName(i);
98  }
99  }
100 
101  return runningTotal;
102  }
103 
104  private void Dispose(bool disposing) {
105  if (disposing) {
106  foreach (var slice in fileSlices) {
107  if (slice != null)
108  slice.Dispose();
109  }
110 
111  fileSlices.Clear();
112  fileSlices = null;
113  }
114  }
115 
116  public void Dispose() {
117  Dispose(true);
118  GC.SuppressFinalize(this);
119  }
120 
121  public bool Exists {
122  get { return FileSystem.FileExists(SliceFileName(0)); }
123  }
124 
125  public long Length {
126  get {
127  lock (objectLock) {
128  return IsOpen ? trueFileLength : DiscoverSize();
129  }
130  }
131  }
132 
133  public bool IsReadOnly { get; private set; }
134 
135  public bool Delete() {
136  // The number of files
137  int countFiles = FileCount;
138  // Delete each file from back to front
139  for (int i = countFiles - 1; i >= 0; --i) {
140  string f = SliceFileName(i);
141  bool deleteSuccess = new FileStoreData(f).Delete();
142  if (!deleteSuccess)
143  return false;
144  }
145  return true;
146  }
147 
148  public void Open(bool readOnly) {
149  lock (objectLock) {
150  // Does the file exist?
151  string f = SliceFileName(0);
152  bool openExisting = FileSystem.FileExists(f);
153 
154  // If the file already exceeds the threshold and there isn't a secondary
155  // file then we need to convert the file.
156  if (openExisting && f.Length > MaxFileSlice) {
157  string f2 = SliceFileName(1);
158  if (FileSystem.FileExists(f2))
159  throw new IOException("File length exceeds maximum slice size setting.");
160 
161  // We need to scatter the file.
162  if (readOnly)
163  throw new IOException("Unable to convert to a scattered store because Read-only.");
164  }
165 
166  // Setup the first file slice
167  var slice = new FileStoreData(f);
168  slice.Open(readOnly);
169 
170  fileSlices.Add(slice);
171  long runningLength = slice.Length;
172 
173  // If we are opening a store that exists already, there may be other
174  // slices we need to setup.
175  if (openExisting) {
176  int i = 1;
177  string slicePart = SliceFileName(i);
178  while (FileSystem.FileExists(slicePart)) {
179  // Create the new slice information for this part of the file.
180  slice = new FileStoreData(slicePart);
181  slice.Open(readOnly);
182 
183  fileSlices.Add(slice);
184  runningLength += slice.Length;
185 
186  ++i;
187  slicePart = SliceFileName(i);
188  }
189  }
190 
191  trueFileLength = runningLength;
192  IsOpen = true;
193  IsReadOnly = readOnly;
194  }
195 
196  }
197 
198  public void Close() {
199  lock (objectLock) {
200  foreach (var slice in fileSlices) {
201  slice.Close();
202  }
203  }
204  }
205 
206  public int Read(long position, byte[] buffer, int offset, int length) {
207  // Reads the array (potentially across multiple slices).
208  int count = 0;
209  while (length > 0) {
210  int fileI = (int)(position / MaxFileSlice);
211  long fileP = (position % MaxFileSlice);
212  int fileLen = (int)System.Math.Min((long)length, MaxFileSlice - fileP);
213 
214  FileStoreData slice;
215  lock (objectLock) {
216  // Return if out of bounds.
217  if (fileI < 0 || fileI >= fileSlices.Count) {
218  // Error if not open
219  if (!IsOpen)
220  throw new IOException("Store not open.");
221 
222  return 0;
223  }
224  slice = fileSlices[fileI];
225  }
226 
227  int readCount =slice.Read(fileP, buffer, offset, fileLen);
228  if (readCount == 0)
229  break;
230 
231  position += readCount;
232  offset += readCount;
233  length -= readCount;
234  count += readCount;
235  }
236 
237  return count;
238  }
239 
240  public void Write(long position, byte[] buffer, int offset, int length) {
241  // Writes the array (potentially across multiple slices).
242  while (length > 0) {
243  var fileI = (int)(position / MaxFileSlice);
244  var fileP = (position % MaxFileSlice);
245  var fileLen = (int)System.Math.Min((long)length, MaxFileSlice - fileP);
246 
247  FileStoreData slice;
248  lock (objectLock) {
249  // Return if out of bounds.
250  if (fileI < 0 || fileI >= fileSlices.Count) {
251  if (!IsOpen)
252  throw new IOException("Store not open.");
253 
254  return;
255  }
256  slice = fileSlices[fileI];
257  }
258 
259  slice.Write(fileP, buffer, offset, fileLen);
260 
261  position += fileLen;
262  offset += fileLen;
263  length -= fileLen;
264  }
265  }
266 
267  public void Flush() {
268  lock (objectLock) {
269  foreach (var slice in fileSlices) {
270  slice.Flush();
271  }
272  }
273  }
274 
275  public void SetLength(long value) {
276  lock (objectLock) {
277  // The size we need to grow the data area
278  long totalSizeToGrow = value - trueFileLength;
279  // Assert that we aren't shrinking the data area size.
280  if (totalSizeToGrow < 0) {
281  throw new IOException("Unable to make the data area size " +
282  "smaller for this type of store.");
283  }
284 
285  while (totalSizeToGrow > 0) {
286  // Grow the last slice by this size
287  int last = fileSlices.Count - 1;
288  var slice = fileSlices[last];
289  long oldSliceLength = slice.Length;
290  long toGrow = System.Math.Min(totalSizeToGrow, (MaxFileSlice - oldSliceLength));
291 
292  // Flush the buffer and set the length of the file
293  slice.SetLength(oldSliceLength + toGrow);
294  slice.Flush();
295 
296  totalSizeToGrow -= toGrow;
297  // Create a new empty slice if we need to extend the data area
298  if (totalSizeToGrow > 0) {
299  string sliceFile = SliceFileName(last + 1);
300 
301  slice = new FileStoreData(sliceFile);
302  slice.Open(false);
303 
304  fileSlices.Add(slice);
305  }
306  }
307  trueFileLength = value;
308  }
309  }
310  }
311 }
void Write(long position, byte[] buffer, int offset, int length)
Writes a given buffer into the block, starting at the absolute position given.
ScatteringFileStoreData(IFileSystem fileSystem, string basePath, string fileName, string fileExtention, int maxFileSlice)
void Close()
Closes the block and make it unavailable.
void Write(long position, byte[] buffer, int offset, int length)
Writes a given buffer into the block, starting at the absolute position given.
bool Delete()
Deletes the data block.
int Read(long position, byte[] buffer, int offset, int length)
Reads a given amount of data from the block, starting at the absolute position given and copying into...
void SetLength(long value)
Sets the length of the data block.
An interface for low level store data access methods.
Definition: IStoreData.cs:23
A data store that is backed by a file located at the path given.
void Open(bool readOnly)
Opens the data block and make it ready to be accessed.
int Read(long position, byte[] buffer, int offset, int length)
Reads a given amount of data from the block, starting at the absolute position given and copying into...
void Flush()
Flushes the data written in the temporary store of the block to the underlying medium.