18 using System.Collections.Generic;
27 namespace Deveel.Data.Store {
33 private readonly
object checkPointLock =
new object();
35 private IDictionary<int, SingleFileStore>
stores;
41 public const string DefaultFileExtension =
"db";
43 private const int Magic = 0xf09a671;
47 throw new ArgumentNullException(
"context");
49 this.context = context;
51 Configure(configuration);
61 var basePath = config.GetString(
"database.basePath");
62 var fileName = config.GetString(
"database.fileName");
63 var fullPath = config.GetString(
"database.fullPath");
65 if (String.IsNullOrEmpty(basePath)) {
66 if (String.IsNullOrEmpty(fullPath)) {
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)) {
78 }
else if (String.IsNullOrEmpty(fullPath)) {
79 FileName = FileSystem.CombinePath(basePath, fileName);
82 if (String.IsNullOrEmpty(FileName))
86 public string FileName {
get; set; }
88 private string TempFileName {
89 get {
return String.Format(
"{0}.tmp", FileName); }
92 private string LockFileName {
93 get {
return String.Format(
"{0}.lock", FileName); }
96 public bool ReadOnly {
get; set; }
100 GC.SuppressFinalize(
this);
105 if (stores != null) {
106 foreach (var store
in stores.Values) {
120 public bool IsReadOnly {
get; set; }
123 get {
return context.ResolveService<
IFileSystem>(); }
126 public object CheckPointLock {
127 get {
return checkPointLock; }
131 bool created =
false;
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));
141 dataFile = FileSystem.CreateFile(FileName);
147 LoadStores(dataFile);
149 using (var stream =
new FileStream(dataFile)) {
150 WriteHeaders(stream, 24);
156 if (dataFile != null)
162 using (var fileStream =
new FileStream(dataFile)) {
163 using (var reader =
new BinaryReader(fileStream, Encoding.Unicode)) {
170 var magic = reader.ReadInt32();
173 throw new IOException(
"The magic number in the header is invalid.");
175 var version = reader.ReadInt32();
176 var lastModified = reader.ReadInt64();
177 var storeCount = reader.ReadInt32();
180 storeId = reader.ReadInt32();
182 storeInfo =
new Dictionary<int, StoreInfo>(storeCount);
184 for (
int i = 0; i < storeCount; i++) {
185 var strLength = reader.ReadInt32();
186 var nameChars = reader.ReadChars(strLength);
188 var name =
new string(nameChars);
189 var
id = reader.ReadInt32();
190 var offset = reader.ReadInt64();
191 var size = reader.ReadInt64();
193 storeInfo[id] =
new StoreInfo(name,
id, offset, size);
195 if (nameIdMap == null)
196 nameIdMap =
new Dictionary<string, int>();
198 nameIdMap[name] = id;
204 if (storeInfo == null ||
205 !storeInfo.TryGetValue(
id, out info))
208 var size = info.
Size;
209 var offset = info.Offset;
211 using (var dataFile = FileSystem.OpenFile(FileName,
true)) {
212 using (var stream =
new FileStream(dataFile)) {
213 stream.Seek(offset, SeekOrigin.Begin);
215 var buffer =
new byte[size];
217 var outputStream =
new MemoryStream();
220 stream.Read(buffer, 0, (
int) size);
222 outputStream.Write(buffer, 0, buffer.Length);
224 if (outputStream.Length != size)
225 throw new IOException(
"Corruption when reading the store.");
234 return TryFindStore(name, out store);
238 return CreateStore(name);
242 lock (checkPointLock) {
244 if (TryFindStore(name, out store))
245 throw new IOException(
string.Format(
"The store '{0}' already exists in this database.", name));
247 if (nameIdMap == null)
248 nameIdMap =
new Dictionary<string, int>();
251 stores =
new Dictionary<int, SingleFileStore>();
257 nameIdMap[name] = id;
263 return OpenStore(name);
267 lock (checkPointLock) {
269 if (!TryFindStore(name, out store))
270 throw new IOException(
string.Format(
"The store '{0}' does not exist in this database.", name));
284 if (!TryFindStore(store.
Name, out fileStore))
285 throw new IOException(
"The store was not found in this database.");
289 }
catch (IOException) {
291 }
catch (Exception ex) {
292 throw new IOException(
"Unable to close the store.", ex);
302 if (stores == null || !stores.ContainsKey(store.
Id))
305 return stores.Remove(store.
Id);
306 }
catch (IOException) {
308 }
catch (Exception ex) {
309 throw new IOException(
"Unable to delete the store.", ex);
311 if (nameIdMap != null)
312 nameIdMap.Remove(store.
Name);
317 lock (checkPointLock) {
319 if (FileSystem.FileExists(TempFileName))
320 FileSystem.DeleteFile(TempFileName);
322 using (var dataFile = FileSystem.CreateFile(TempFileName)) {
323 using (var stream =
new FileStream(dataFile)) {
324 var dataStartOffset = GetDataStartOffset();
325 WriteHeaders(stream, dataStartOffset);
332 if (FileSystem.FileExists(FileName))
333 FileSystem.DeleteFile(FileName);
335 FileSystem.RenameFile(TempFileName, FileName);
336 }
catch (IOException) {
338 }
catch (Exception ex) {
339 throw new IOException(
"An error occurred while saving data to database file.", ex);
347 foreach (var store
in stores.Values) {
348 var nameLength = Encoding.Unicode.GetByteCount(store.Name);
350 offset += 4 + nameLength + 4 + 8 + 8;
357 if (stores != null) {
358 foreach (var store
in stores.Values) {
359 store.WriteTo(stream);
365 var nameLength = store.
Name.Length;
366 var name = store.
Name;
370 writer.Write(nameLength);
371 writer.Write(name.ToCharArray());
374 writer.Write(offset);
377 storeInfo[store.
Id] =
new StoreInfo(name,
id, offset, size);
385 var writer =
new BinaryWriter(stream, Encoding.Unicode);
388 writer.Write(DateTime.UtcNow.Ticks);
390 var storeCount = stores == null ? 0 : stores.Count;
391 var topId = FindMaxStoreId();
393 writer.Write(storeCount);
396 long offset = dataStartOffset;
398 if (stores != null) {
399 storeInfo =
new Dictionary<int, StoreInfo>(stores.Count);
401 foreach (var store
in stores.Values) {
402 offset = WriteStoreInfo(writer, offset, store);
408 return stores == null ? 0 : stores.Max(x => x.Value.Id);
413 if (nameIdMap == null || !nameIdMap.TryGetValue(storeName, out
id)) {
418 if (stores == null || !stores.TryGetValue(
id, out store)) {
426 public void Lock(
string lockName) {
428 if (!TryFindStore(lockName, out store))
436 if (!TryFindStore(lockName, out store))
444 [DebuggerDisplay(
"[{Id}]{Name} ({Offset} - {Size})")]
446 public StoreInfo(
string name,
int id,
long offset,
long size) {
453 public string Name {
get;
private set; }
455 public int Id {
get;
private set; }
457 public long Offset {
get;
private set; }
459 public long Size {
get;
private set; }
void Configure(IConfiguration config)
bool CloseStore(SingleFileStore store)
long GetDataStartOffset()
SingleFileStore OpenStore(string name)
SingleFileStore CreateStore(string name)
The context of a single database within a system.
Stream LoadStoreData(int id)
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...
IDictionary< string, int > nameIdMap
StoreInfo(string name, int id, long offset, long size)
void Unlock(string lockName)
IDictionary< int, StoreInfo > storeInfo
bool DeleteStore(SingleFileStore store)
void LoadStores(IFile dataFile)
void Lock(string lockName)
void LoadHeaders(BinaryReader reader)
Defines the contract for the configuration node of a component within the system or of the system its...
void WriteHeaders(Stream stream, long dataStartOffset)
void WriteStores(Stream stream)
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.
void Dispose(bool disposing)
bool StoreExists(string name)
A store is a resource where areas can be allocated and freed to store information (a memory allocator...
override void Unlock()
This method is called after the end of a sequence of Write commands between consistant states of some...