DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
SingleFileStoreSystem.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.Diagnostics;
20 using System.IO;
21 using System.Linq;
22 using System.Text;
23 
25 using Deveel.Data.Services;
26 
27 namespace Deveel.Data.Store {
28  public sealed class SingleFileStoreSystem : IStoreSystem {
30 
31  private bool disposed;
32 
33  private readonly object checkPointLock = new object();
34 
35  private IDictionary<int, SingleFileStore> stores;
36  private IDictionary<string, int> nameIdMap;
37  private IDictionary<int, StoreInfo> storeInfo;
38 
39  private int storeId;
40 
41  public const string DefaultFileExtension = "db";
42 
43  private const int Magic = 0xf09a671;
44 
45  public SingleFileStoreSystem(IDatabaseContext context, IConfiguration configuration) {
46  if (context == null)
47  throw new ArgumentNullException("context");
48 
49  this.context = context;
50 
51  Configure(configuration);
52 
53  OpenOrCreateFile();
54  }
55 
57  Dispose(false);
58  }
59 
60  private void Configure(IConfiguration config) {
61  var basePath = config.GetString("database.basePath");
62  var fileName = config.GetString("database.fileName");
63  var fullPath = config.GetString("database.fullPath");
64 
65  if (String.IsNullOrEmpty(basePath)) {
66  if (String.IsNullOrEmpty(fullPath)) {
67  FileName = fileName;
68  } else {
69  FileName = fullPath;
70  }
71  } else if (String.IsNullOrEmpty(fileName)) {
72  if (!String.IsNullOrEmpty(basePath)) {
73  fileName = String.Format("{0}.{1}", context.DatabaseName(), DefaultFileExtension);
74  FileName = FileSystem.CombinePath(basePath, fileName);
75  } else if (!String.IsNullOrEmpty(fullPath)) {
76  FileName = fullPath;
77  }
78  } else if (String.IsNullOrEmpty(fullPath)) {
79  FileName = FileSystem.CombinePath(basePath, fileName);
80  }
81 
82  if (String.IsNullOrEmpty(FileName))
83  throw new DatabaseConfigurationException("Could not configure the file name of the database.");
84  }
85 
86  public string FileName { get; set; }
87 
88  private string TempFileName {
89  get { return String.Format("{0}.tmp", FileName); }
90  }
91 
92  private string LockFileName {
93  get { return String.Format("{0}.lock", FileName); }
94  }
95 
96  public bool ReadOnly { get; set; }
97 
98  public void Dispose() {
99  Dispose(true);
100  GC.SuppressFinalize(this);
101  }
102 
103  private void Dispose(bool disposing) {
104  if (disposing) {
105  if (stores != null) {
106  foreach (var store in stores.Values) {
107  if (store != null)
108  store.Dispose();
109  }
110 
111  stores.Clear();
112  }
113  }
114 
115  disposed = true;
116  storeInfo = null;
117  stores = null;
118  }
119 
120  public bool IsReadOnly { get; set; }
121 
122  private IFileSystem FileSystem {
123  get { return context.ResolveService<IFileSystem>(); }
124  }
125 
126  public object CheckPointLock {
127  get { return checkPointLock; }
128  }
129 
130  private void OpenOrCreateFile() {
131  bool created = false;
132 
133  IFile dataFile;
134 
135  if (FileSystem.FileExists(FileName)) {
136  dataFile = FileSystem.OpenFile(FileName, ReadOnly);
137  } else if (ReadOnly) {
138  throw new InvalidOperationException(
139  string.Format("The file '{0}' does not exist and the store is configured to be read-only.", FileName));
140  } else {
141  dataFile = FileSystem.CreateFile(FileName);
142  created = true;
143  }
144 
145  try {
146  if (!created) {
147  LoadStores(dataFile);
148  } else {
149  using (var stream = new FileStream(dataFile)) {
150  WriteHeaders(stream, 24);
151 
152  stream.Flush();
153  }
154  }
155  } finally {
156  if (dataFile != null)
157  dataFile.Dispose();
158  }
159  }
160 
161  private void LoadStores(IFile dataFile) {
162  using (var fileStream = new FileStream(dataFile)) {
163  using (var reader = new BinaryReader(fileStream, Encoding.Unicode)) {
164  LoadHeaders(reader);
165  }
166  }
167  }
168 
169  private void LoadHeaders(BinaryReader reader) {
170  var magic = reader.ReadInt32();
171 
172  if (magic != Magic)
173  throw new IOException("The magic number in the header is invalid.");
174 
175  var version = reader.ReadInt32();
176  var lastModified = reader.ReadInt64();
177  var storeCount = reader.ReadInt32();
178 
179  // the maximum number of the stores
180  storeId = reader.ReadInt32();
181 
182  storeInfo = new Dictionary<int, StoreInfo>(storeCount);
183 
184  for (int i = 0; i < storeCount; i++) {
185  var strLength = reader.ReadInt32();
186  var nameChars = reader.ReadChars(strLength);
187 
188  var name = new string(nameChars);
189  var id = reader.ReadInt32();
190  var offset = reader.ReadInt64();
191  var size = reader.ReadInt64();
192 
193  storeInfo[id] = new StoreInfo(name, id, offset, size);
194 
195  if (nameIdMap == null)
196  nameIdMap = new Dictionary<string, int>();
197 
198  nameIdMap[name] = id;
199  }
200  }
201 
202  internal Stream LoadStoreData(int id) {
203  StoreInfo info;
204  if (storeInfo == null ||
205  !storeInfo.TryGetValue(id, out info))
206  return null;
207 
208  var size = info.Size;
209  var offset = info.Offset;
210 
211  using (var dataFile = FileSystem.OpenFile(FileName, true)) {
212  using (var stream = new FileStream(dataFile)) {
213  stream.Seek(offset, SeekOrigin.Begin);
214 
215  var buffer = new byte[size];
216 
217  var outputStream = new MemoryStream();
218 
219  // TODO: support larger portions...
220  stream.Read(buffer, 0, (int) size);
221 
222  outputStream.Write(buffer, 0, buffer.Length);
223 
224  if (outputStream.Length != size)
225  throw new IOException("Corruption when reading the store.");
226 
227  return outputStream;
228  }
229  }
230  }
231 
232  public bool StoreExists(string name) {
233  SingleFileStore store;
234  return TryFindStore(name, out store);
235  }
236 
238  return CreateStore(name);
239  }
240 
241  public SingleFileStore CreateStore(string name) {
242  lock (checkPointLock) {
243  SingleFileStore store;
244  if (TryFindStore(name, out store))
245  throw new IOException(string.Format("The store '{0}' already exists in this database.", name));
246 
247  if (nameIdMap == null)
248  nameIdMap = new Dictionary<string, int>();
249 
250  if (stores == null)
251  stores = new Dictionary<int, SingleFileStore>();
252 
253  var id = ++storeId;
254  store = new SingleFileStore(this, name, id);
255  store.Open();
256  stores[id] = store;
257  nameIdMap[name] = id;
258  return store;
259  }
260  }
261 
263  return OpenStore(name);
264  }
265 
266  public SingleFileStore OpenStore(string name) {
267  lock (checkPointLock) {
268  SingleFileStore store;
269  if (!TryFindStore(name, out store))
270  throw new IOException(string.Format("The store '{0}' does not exist in this database.", name));
271 
272  store.Open();
273  return store;
274  }
275  }
276 
278  return CloseStore((SingleFileStore) store);
279  }
280 
281  public bool CloseStore(SingleFileStore store) {
282  try {
283  SingleFileStore fileStore;
284  if (!TryFindStore(store.Name, out fileStore))
285  throw new IOException("The store was not found in this database.");
286 
287  fileStore.Close();
288  return true;
289  } catch (IOException) {
290  throw;
291  } catch (Exception ex) {
292  throw new IOException("Unable to close the store.", ex);
293  }
294  }
295 
297  return DeleteStore((SingleFileStore) store);
298  }
299 
300  public bool DeleteStore(SingleFileStore store) {
301  try {
302  if (stores == null || !stores.ContainsKey(store.Id))
303  return false;
304 
305  return stores.Remove(store.Id);
306  } catch (IOException) {
307  throw;
308  } catch (Exception ex) {
309  throw new IOException("Unable to delete the store.", ex);
310  } finally {
311  if (nameIdMap != null)
312  nameIdMap.Remove(store.Name);
313  }
314  }
315 
316  public void SetCheckPoint() {
317  lock (checkPointLock) {
318  try {
319  if (FileSystem.FileExists(TempFileName))
320  FileSystem.DeleteFile(TempFileName);
321 
322  using (var dataFile = FileSystem.CreateFile(TempFileName)) {
323  using (var stream = new FileStream(dataFile)) {
324  var dataStartOffset = GetDataStartOffset();
325  WriteHeaders(stream, dataStartOffset);
326  WriteStores(stream);
327 
328  stream.Flush();
329  }
330  }
331 
332  if (FileSystem.FileExists(FileName))
333  FileSystem.DeleteFile(FileName);
334 
335  FileSystem.RenameFile(TempFileName, FileName);
336  } catch (IOException) {
337  throw;
338  } catch (Exception ex) {
339  throw new IOException("An error occurred while saving data to database file.", ex);
340  }
341  }
342  }
343 
344  private long GetDataStartOffset() {
345  long offset = 24;
346 
347  foreach (var store in stores.Values) {
348  var nameLength = Encoding.Unicode.GetByteCount(store.Name);
349 
350  offset += 4 + nameLength + 4 + 8 + 8;
351  }
352 
353  return offset;
354  }
355 
356  private void WriteStores(Stream stream) {
357  if (stores != null) {
358  foreach (var store in stores.Values) {
359  store.WriteTo(stream);
360  }
361  }
362  }
363 
364  private long WriteStoreInfo(BinaryWriter writer, long offset, SingleFileStore store) {
365  var nameLength = store.Name.Length;
366  var name = store.Name;
367  var id = store.Id;
368  var size = store.DataLength;
369 
370  writer.Write(nameLength);
371  writer.Write(name.ToCharArray());
372 
373  writer.Write(id);
374  writer.Write(offset);
375  writer.Write(size);
376 
377  storeInfo[store.Id] = new StoreInfo(name, id, offset, size);
378 
379  offset += store.DataLength;
380 
381  return offset;
382  }
383 
384  private void WriteHeaders(Stream stream, long dataStartOffset) {
385  var writer = new BinaryWriter(stream, Encoding.Unicode);
386  writer.Write(Magic);
387  writer.Write(1);
388  writer.Write(DateTime.UtcNow.Ticks);
389 
390  var storeCount = stores == null ? 0 : stores.Count;
391  var topId = FindMaxStoreId();
392 
393  writer.Write(storeCount);
394  writer.Write(topId);
395 
396  long offset = dataStartOffset;
397 
398  if (stores != null) {
399  storeInfo = new Dictionary<int, StoreInfo>(stores.Count);
400 
401  foreach (var store in stores.Values) {
402  offset = WriteStoreInfo(writer, offset, store);
403  }
404  }
405  }
406 
407  private int FindMaxStoreId() {
408  return stores == null ? 0 : stores.Max(x => x.Value.Id);
409  }
410 
411  private bool TryFindStore(string storeName, out SingleFileStore store) {
412  int id;
413  if (nameIdMap == null || !nameIdMap.TryGetValue(storeName, out id)) {
414  store = null;
415  return false;
416  }
417 
418  if (stores == null || !stores.TryGetValue(id, out store)) {
419  store = null;
420  return false;
421  }
422 
423  return true;
424  }
425 
426  public void Lock(string lockName) {
427  SingleFileStore store;
428  if (!TryFindStore(lockName, out store))
429  return;
430 
431  store.Lock();
432  }
433 
434  public void Unlock(string lockName) {
435  SingleFileStore store;
436  if (!TryFindStore(lockName, out store))
437  return;
438 
439  store.Unlock();
440  }
441 
442  #region StoreInfo
443 
444  [DebuggerDisplay("[{Id}]{Name} ({Offset} - {Size})")]
445  class StoreInfo {
446  public StoreInfo(string name, int id, long offset, long size) {
447  Name = name;
448  Id = id;
449  Offset = offset;
450  Size = size;
451  }
452 
453  public string Name { get; private set; }
454 
455  public int Id { get; private set; }
456 
457  public long Offset { get; private set; }
458 
459  public long Size { get; private set; }
460  }
461 
462  #endregion
463  }
464 }
The context of a single database within a system.
IStore CreateStore(String name)
Creates and returns a new persistent Store object given the unique name of the store.
override void Lock()
This method is called before the start of a sequence of Write commands between consistant states of s...
bool TryFindStore(string storeName, out SingleFileStore store)
SingleFileStoreSystem(IDatabaseContext context, IConfiguration configuration)
An object that creates and manages the IStore objects that the database engine uses to represent itse...
Definition: IStoreSystem.cs:31
StoreInfo(string name, int id, long offset, long size)
IDictionary< int, StoreInfo > storeInfo
Defines the contract for the configuration node of a component within the system or of the system its...
void WriteHeaders(Stream stream, long dataStartOffset)
IDictionary< int, SingleFileStore > stores
bool CloseStore(IStore store)
Closes a store that has been either created or opened with the CreateStore or OpenStore methods...
IStore OpenStore(String name)
Opens an existing persistent Store object in the system and returns the IStore object that contains i...
long WriteStoreInfo(BinaryWriter writer, long offset, SingleFileStore store)
bool DeleteStore(IStore store)
Permanently deletes a store from the system - use with care!
void SetCheckPoint()
Sets a new check point at the current state of this store system.
A store is a resource where areas can be allocated and freed to store information (a memory allocator...
Definition: IStore.cs:56
override void Unlock()
This method is called after the end of a sequence of Write commands between consistant states of some...