Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
FileOdbBackend.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Text;
7 using SiliconStudio.Core.IO;
8 using SiliconStudio.Core.Serialization.Assets;
9 
10 namespace SiliconStudio.Core.Storage
11 {
12  /// <summary>
13  /// Object Database Backend (ODB) implementation using <see cref="VirtualFileSystem"/>
14  /// </summary>
15  public class FileOdbBackend : IOdbBackend
16  {
17  private const int WriteBufferSize = 1024;
18  private bool isReadOnly;
19 
20  // Resolve provider once at initialization
21  private readonly IVirtualFileProvider virtualFileProvider;
22  private readonly string vfsRootUrl;
23  private readonly string vfsTempUrl;
24  private AssetIndexMap assetIndexMap;
25 
26  /// <inheritdoc/>
28  {
29  get { return assetIndexMap; }
30  }
31 
32  public FileOdbBackend(string vfsRootUrl, bool isReadOnly, string indexName = "index")
33  {
34  var resolveProviderResult = VirtualFileSystem.ResolveProvider(vfsRootUrl, true);
35  virtualFileProvider = resolveProviderResult.Provider;
36  this.vfsRootUrl = resolveProviderResult.Path;
37  vfsTempUrl = this.vfsRootUrl + "/tmp/";
38 
39  // Ensure directories exists
40  if (!virtualFileProvider.DirectoryExists(this.vfsRootUrl))
41  virtualFileProvider.CreateDirectory(this.vfsRootUrl);
42 
43  this.isReadOnly = isReadOnly;
44 
45  assetIndexMap = Serialization.Assets.AssetIndexMap.Load(vfsRootUrl + VirtualFileSystem.DirectorySeparatorChar + indexName, isReadOnly);
46  if (!isReadOnly && !virtualFileProvider.DirectoryExists(vfsTempUrl))
47  {
48  try
49  {
50  virtualFileProvider.CreateDirectory(vfsTempUrl);
51  }
52  catch (Exception)
53  {
54  this.isReadOnly = true;
55  }
56  }
57  }
58 
59  public bool IsReadOnly
60  {
61  get { return isReadOnly; }
62  }
63 
64  /// <inheritdoc/>
66  {
67  var url = BuildUrl(vfsRootUrl, objectId);
68 
69  // Try to early exit if file does not exists while opening, so that it doesn't
70  // throw a file not found exception for default logic.
71  if (!virtualFileProvider.FileExists(url))
72  {
73  if (mode == VirtualFileMode.Open || mode == VirtualFileMode.Truncate)
74  return null;
75 
76  // Otherwise, file creation is allowed, so make sure directory exists
77  virtualFileProvider.CreateDirectory(ExtractPath(url));
78 
79  }
80 
81  try
82  {
83  return virtualFileProvider.OpenStream(url, mode, access, share);
84  }
85  catch (FileNotFoundException)
86  {
87  return null;
88  }
89 #if !SILICONSTUDIO_PLATFORM_WINDOWS_RUNTIME
90  catch (DirectoryNotFoundException)
91  {
92  return null;
93  }
94 #endif
95  }
96 
97  /// <inheritdoc/>
98  public virtual int GetSize(ObjectId objectId)
99  {
100  var url = BuildUrl(vfsRootUrl, objectId);
101  using (var file = virtualFileProvider.OpenStream(url, VirtualFileMode.Open, VirtualFileAccess.Read))
102  {
103  return checked((int)file.Length);
104  }
105  }
106 
107  /// <inheritdoc/>
108  public virtual bool Exists(ObjectId objectId)
109  {
110  var url = BuildUrl(vfsRootUrl, objectId);
111  return virtualFileProvider.FileExists(url);
112  }
113 
114  /// <inheritdoc/>
115  public virtual ObjectId Write(ObjectId objectId, Stream dataStream, int length, bool forceWrite = false)
116  {
117  if (objectId == ObjectId.Empty)
118  {
119  // This should be avoided
120  using (var digestStream = new DigestStream(Stream.Null))
121  {
122  dataStream.CopyTo(digestStream);
123  objectId = digestStream.CurrentHash;
124  }
125 
126  dataStream.Seek(0, SeekOrigin.Begin);
127  }
128 
129  var url = BuildUrl(vfsRootUrl, objectId);
130 
131  if (!forceWrite && virtualFileProvider.FileExists(url))
132  return objectId;
133 
134  virtualFileProvider.CreateDirectory(ExtractPath(url));
135  using (var file = virtualFileProvider.OpenStream(url, VirtualFileMode.Create, VirtualFileAccess.Write))
136  {
137  // TODO: Fast case for NativeStream. However we still need a file implementation of NativeStream.
138  var buffer = new byte[WriteBufferSize];
139  for (int offset = 0; offset < length; offset += WriteBufferSize)
140  {
141  int blockSize = length - offset;
142  if (blockSize > WriteBufferSize)
143  blockSize = WriteBufferSize;
144 
145  dataStream.Read(buffer, 0, blockSize);
146  file.Write(buffer, 0, blockSize);
147  }
148  }
149 
150  return objectId;
151  }
152 
153  /// <inheritdoc/>
155  {
156  if (isReadOnly)
157  throw new InvalidOperationException("Read-only backend.");
158 
159  string tmpFileName = vfsTempUrl + Guid.NewGuid() + ".tmp";
160  Stream stream = virtualFileProvider.OpenStream(tmpFileName, VirtualFileMode.Create, VirtualFileAccess.Write);
161  return new DigestStream(stream, tmpFileName) { Disposed = x => SaveStream(x) };
162  }
163 
164  /// <inheritdoc/>
165  private ObjectId SaveStream(OdbStreamWriter stream)
166  {
167  ObjectId objId = stream.CurrentHash;
168  string fileUrl = BuildUrl(vfsRootUrl, objId);
169 
170  string temporaryFilePath = stream.TemporaryName;
171 
172  // File may already exists, in this case we decide to not override it.
173  if (!virtualFileProvider.FileExists(fileUrl))
174  {
175  try
176  {
177  // Remove the second part of ObjectId to get the path (cf BuildUrl)
178  virtualFileProvider.CreateDirectory(fileUrl.Substring(0, fileUrl.Length - (ObjectId.HashStringLength - 2)));
179  virtualFileProvider.FileMove(temporaryFilePath, BuildUrl(vfsRootUrl, objId));
180  }
181  catch (IOException e)
182  {
183  // Ignore only IOException "The destination file already exists."
184  // because other exceptions that we want to catch might inherit from IOException.
185  // This happens if two FileMove were performed at the same time.
186  if (e.GetType() != typeof(IOException))
187  throw;
188 
189  // But we should still clean our temporary file
190  virtualFileProvider.FileDelete(temporaryFilePath);
191  }
192  }
193  else
194  {
195  // But we should still clean our temporary file
196  virtualFileProvider.FileDelete(temporaryFilePath);
197  }
198 
199  return objId;
200  }
201 
202  /// <inheritdoc/>
203  public void Delete(ObjectId objectId)
204  {
205  var url = BuildUrl(vfsRootUrl, objectId);
206  virtualFileProvider.FileDelete(url);
207  }
208 
209  /// <inheritdoc/>
211  {
212  foreach (var file in virtualFileProvider.ListFiles(vfsRootUrl, "*", VirtualSearchOption.AllDirectories))
213  {
214  if (file.Length >= 2 + ObjectId.HashStringLength)
215  {
218  continue;
219 
220  var objectIdString = new char[ObjectId.HashStringLength];
221  var filePosition = file.Length - ObjectId.HashStringLength - 1;
222  for (int i = 0; i < ObjectId.HashStringLength; ++i)
223  {
224  // Skip /
225  if (i == 2)
226  filePosition++;
227  objectIdString[i] = file[filePosition++];
228  }
229 
230  ObjectId objectId;
231  if (ObjectId.TryParse(new string(objectIdString), out objectId))
232  yield return objectId;
233  }
234  }
235  }
236 
237  public string GetFilePath(ObjectId objectId)
238  {
239  return virtualFileProvider.GetAbsolutePath(BuildUrl(vfsRootUrl, objectId));
240  }
241 
242  private static string ExtractPath(string url)
243  {
244  return url.Substring(0, url.LastIndexOf('/'));
245  }
246 
247  public static string BuildUrl(string vfsRootUrl, ObjectId objectId)
248  {
249  var id = objectId.ToString();
250  var result = new StringBuilder(vfsRootUrl.Length + 2 + ObjectId.HashStringLength);
251  result.Append(vfsRootUrl);
252  result.Append('/');
253  result.Append(id[0]);
254  result.Append(id[1]);
255  result.Append('/');
256  result.Append(id, 2, ObjectId.HashStringLength - 2);
257 
258  return result.ToString();
259  }
260  }
261 }
virtual Stream OpenStream(ObjectId objectId, VirtualFileMode mode=VirtualFileMode.Open, VirtualFileAccess access=VirtualFileAccess.Read, VirtualFileShare share=VirtualFileShare.Read)
Opens a NativeStream of the object with the specified ObjectId. The ObjectId.The mode.The access.The process share mode.A NativeStream opened from the specified ObjectId.
A virtual file provider, that can returns a Stream for a given path.
Virtual abstraction over a file system. It handles access to files, http, packages, path rewrite, etc...
static string BuildUrl(string vfsRootUrl, ObjectId objectId)
virtual ObjectId Write(ObjectId objectId, Stream dataStream, int length, bool forceWrite=false)
Writes an object to the backing store. The backend may need to compute the object ID and return it to...
VirtualFileShare
File share capabilities, equivalent of System.IO.FileShare.
Base class for custom object database backends (ODB).
Definition: IOdbBackend.cs:13
FileOdbBackend(string vfsRootUrl, bool isReadOnly, string indexName="index")
void Delete(ObjectId objectId)
Deletes the specified ObjectId. The object id.
Object Database Backend (ODB) implementation using VirtualFileSystem
VirtualFileAccess
File access equivalent of System.IO.FileAccess.
VirtualFileMode
File mode equivalent of System.IO.FileMode.
static readonly ObjectId Empty
Definition: ObjectId.cs:15
A hash to uniquely identify data.
Definition: ObjectId.cs:13
virtual bool Exists(ObjectId objectId)
Determines weither the object with the specified ObjectId exists. The ObjectId to check existence for...
string GetFilePath(ObjectId objectId)
Returns the file path corresponding to the given id (in the VFS domain), if appliable.
IEnumerable< ObjectId > EnumerateObjects()
Enumerates the object stored in this backend.
OdbStreamWriter CreateStream()
Creates a stream that will be saved to database when closed and/or disposed. a stream writer that sho...
static bool TryParse(string input, out ObjectId result)
Tries to parse an ObjectId from a string.
Definition: ObjectId.cs:102
virtual int GetSize(ObjectId objectId)
Requests that this backend read an object's length (but not its contents). The ObjectId.The object size.