PageRenderTime 43ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Database/Server/RavenFS/Controllers/FilesController.cs

https://github.com/nwendel/ravendb
C# | 456 lines | 362 code | 91 blank | 3 comment | 31 complexity | c667bce0bc7469a66aa099f27acc585a MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. using Raven.Abstractions.Data;
  2. using Raven.Abstractions.Exceptions;
  3. using Raven.Abstractions.Extensions;
  4. using Raven.Abstractions.FileSystem;
  5. using Raven.Abstractions.FileSystem.Notifications;
  6. using Raven.Abstractions.Logging;
  7. using Raven.Abstractions.Util.Encryptors;
  8. using Raven.Abstractions.Util.Streams;
  9. using Raven.Database.Server.RavenFS.Extensions;
  10. using Raven.Database.Server.RavenFS.Storage;
  11. using Raven.Database.Server.RavenFS.Util;
  12. using Raven.Imports.Newtonsoft.Json.Linq;
  13. using Raven.Json.Linq;
  14. using System;
  15. using System.Collections.Generic;
  16. using System.Globalization;
  17. using System.IO;
  18. using System.Linq;
  19. using System.Net;
  20. using System.Net.Http;
  21. using System.Net.Http.Headers;
  22. using System.Text.RegularExpressions;
  23. using System.Threading;
  24. using System.Threading.Tasks;
  25. using System.Web.Http;
  26. namespace Raven.Database.Server.RavenFS.Controllers
  27. {
  28. public class FilesController : RavenFsApiController
  29. {
  30. private static readonly ILog log = LogManager.GetCurrentClassLogger();
  31. [HttpGet]
  32. [Route("fs/{fileSystemName}/files")]
  33. public HttpResponseMessage Get()
  34. {
  35. int results;
  36. var keys = Search.Query(null, null, Paging.Start, Paging.PageSize, out results);
  37. var list = new List<FileHeader>();
  38. Storage.Batch(accessor => list.AddRange(keys.Select(accessor.ReadFile).Where(x => x != null)));
  39. return this.GetMessageWithObject(list, HttpStatusCode.OK)
  40. .WithNoCache();
  41. }
  42. [HttpGet]
  43. [Route("fs/{fileSystemName}/files/{*name}")]
  44. public HttpResponseMessage Get(string name)
  45. {
  46. name = RavenFileNameHelper.RavenPath(name);
  47. FileAndPagesInformation fileAndPages = null;
  48. try
  49. {
  50. Storage.Batch(accessor => fileAndPages = accessor.GetFile(name, 0, 0));
  51. }
  52. catch (FileNotFoundException)
  53. {
  54. log.Debug("File '{0}' was not found", name);
  55. throw new HttpResponseException(HttpStatusCode.NotFound);
  56. }
  57. if (fileAndPages.Metadata.Keys.Contains(SynchronizationConstants.RavenDeleteMarker))
  58. {
  59. log.Debug("File '{0}' is not accessible to get (Raven-Delete-Marker set)", name);
  60. throw new HttpResponseException(HttpStatusCode.NotFound);
  61. }
  62. var readingStream = StorageStream.Reading(Storage, name);
  63. var filename = Path.GetFileName(name);
  64. var result = StreamResult(filename, readingStream);
  65. var etag = new Etag(fileAndPages.Metadata.Value<string>(Constants.MetadataEtagField));
  66. fileAndPages.Metadata.Remove(Constants.MetadataEtagField);
  67. WriteHeaders(fileAndPages.Metadata, etag, result);
  68. log.Debug("File '{0}' with etag {1} is being retrieved.", name, etag);
  69. return result.WithNoCache();
  70. }
  71. [HttpDelete]
  72. [Route("fs/{fileSystemName}/files/{*name}")]
  73. public HttpResponseMessage Delete(string name)
  74. {
  75. name = RavenFileNameHelper.RavenPath(name);
  76. try
  77. {
  78. ConcurrencyAwareExecutor.Execute(() => Storage.Batch(accessor =>
  79. {
  80. AssertFileIsNotBeingSynced(name, accessor, true);
  81. var fileAndPages = accessor.GetFile(name, 0, 0);
  82. var metadata = fileAndPages.Metadata;
  83. if (metadata.Keys.Contains(SynchronizationConstants.RavenDeleteMarker))
  84. {
  85. throw new FileNotFoundException();
  86. }
  87. StorageOperationsTask.IndicateFileToDelete(name);
  88. if ( !name.EndsWith(RavenFileNameHelper.DownloadingFileSuffix) &&
  89. // don't create a tombstone for .downloading file
  90. metadata != null) // and if file didn't exist
  91. {
  92. var tombstoneMetadata = new RavenJObject
  93. {
  94. {
  95. SynchronizationConstants.RavenSynchronizationHistory, metadata[SynchronizationConstants.RavenSynchronizationHistory]
  96. },
  97. {
  98. SynchronizationConstants.RavenSynchronizationVersion, metadata[SynchronizationConstants.RavenSynchronizationVersion]
  99. },
  100. {
  101. SynchronizationConstants.RavenSynchronizationSource, metadata[SynchronizationConstants.RavenSynchronizationSource]
  102. }
  103. }.WithDeleteMarker();
  104. Historian.UpdateLastModified(tombstoneMetadata);
  105. accessor.PutFile(name, 0, tombstoneMetadata, true);
  106. accessor.DeleteConfig(RavenFileNameHelper.ConflictConfigNameForFile(name));
  107. // delete conflict item too
  108. }
  109. }), ConcurrencyResponseException);
  110. }
  111. catch (FileNotFoundException)
  112. {
  113. return new HttpResponseMessage(HttpStatusCode.NotFound);
  114. }
  115. Publisher.Publish(new FileChangeNotification { File = FilePathTools.Cannoicalise(name), Action = FileChangeAction.Delete });
  116. log.Debug("File '{0}' was deleted", name);
  117. StartSynchronizeDestinationsInBackground();
  118. return GetEmptyMessage(HttpStatusCode.NoContent);
  119. }
  120. [HttpHead]
  121. [Route("fs/{fileSystemName}/files/{*name}")]
  122. public HttpResponseMessage Head(string name)
  123. {
  124. name = RavenFileNameHelper.RavenPath(name);
  125. FileAndPagesInformation fileAndPages = null;
  126. try
  127. {
  128. Storage.Batch(accessor => fileAndPages = accessor.GetFile(name, 0, 0));
  129. }
  130. catch (FileNotFoundException)
  131. {
  132. log.Debug("Cannot get metadata of a file '{0}' because file was not found", name);
  133. return new HttpResponseMessage(HttpStatusCode.NotFound);
  134. }
  135. if (fileAndPages.Metadata.Keys.Contains(SynchronizationConstants.RavenDeleteMarker))
  136. {
  137. log.Debug("Cannot get metadata of a file '{0}' because file was deleted", name);
  138. return new HttpResponseMessage(HttpStatusCode.NotFound);
  139. }
  140. var httpResponseMessage = GetEmptyMessage();
  141. var etag = new Etag(fileAndPages.Metadata.Value<string>(Constants.MetadataEtagField));
  142. fileAndPages.Metadata.Remove(Constants.MetadataEtagField);
  143. WriteHeaders(fileAndPages.Metadata, etag, httpResponseMessage);
  144. return httpResponseMessage;
  145. }
  146. [HttpGet]
  147. [Route("fs/{fileSystemName}/files/metadata")]
  148. public HttpResponseMessage Metadata([FromUri] string[] fileNames)
  149. {
  150. if (fileNames == null || fileNames.Length == 0)
  151. {
  152. log.Debug("'fileNames' parameter should have a value.");
  153. return GetEmptyMessage(HttpStatusCode.BadRequest);
  154. }
  155. var ravenPaths = fileNames.Where(x => x != null).Select(x => RavenFileNameHelper.RavenPath(x));
  156. var list = new List<FileHeader>();
  157. Storage.Batch(accessor => list.AddRange(ravenPaths.Select(accessor.ReadFile).Where(x => x != null)));
  158. return this.GetMessageWithObject(list, HttpStatusCode.OK)
  159. .WithNoCache();
  160. }
  161. [HttpPost]
  162. [Route("fs/{fileSystemName}/files/{*name}")]
  163. public HttpResponseMessage Post(string name)
  164. {
  165. name = RavenFileNameHelper.RavenPath(name);
  166. var headers = this.GetFilteredMetadataFromHeaders(InnerHeaders);
  167. Historian.UpdateLastModified(headers);
  168. Historian.Update(name, headers);
  169. try
  170. {
  171. ConcurrencyAwareExecutor.Execute(() =>
  172. Storage.Batch(accessor =>
  173. {
  174. AssertFileIsNotBeingSynced(name, accessor, true);
  175. accessor.UpdateFileMetadata(name, headers);
  176. }), ConcurrencyResponseException);
  177. }
  178. catch (FileNotFoundException)
  179. {
  180. log.Debug("Cannot update metadata because file '{0}' was not found", name);
  181. return GetEmptyMessage(HttpStatusCode.NotFound);
  182. }
  183. Search.Index(name, headers);
  184. Publisher.Publish(new FileChangeNotification { File = FilePathTools.Cannoicalise(name), Action = FileChangeAction.Update });
  185. StartSynchronizeDestinationsInBackground();
  186. log.Debug("Metadata of a file '{0}' was updated", name);
  187. //Hack needed by jquery on the client side. We need to find a better solution for this
  188. return GetEmptyMessage(HttpStatusCode.NoContent);
  189. }
  190. [HttpPatch]
  191. [Route("fs/{fileSystemName}/files/{*name}")]
  192. public HttpResponseMessage Patch(string name, string rename)
  193. {
  194. name = RavenFileNameHelper.RavenPath(name);
  195. rename = RavenFileNameHelper.RavenPath(rename);
  196. try
  197. {
  198. ConcurrencyAwareExecutor.Execute(() =>
  199. Storage.Batch(accessor =>
  200. {
  201. AssertFileIsNotBeingSynced(name, accessor, true);
  202. var metadata = accessor.GetFile(name, 0, 0).Metadata;
  203. if (metadata.Keys.Contains(SynchronizationConstants.RavenDeleteMarker))
  204. {
  205. throw new FileNotFoundException();
  206. }
  207. var existingHeader = accessor.ReadFile(rename);
  208. if (existingHeader != null && !existingHeader.Metadata.ContainsKey(SynchronizationConstants.RavenDeleteMarker))
  209. {
  210. throw new HttpResponseException(
  211. Request.CreateResponse(HttpStatusCode.Forbidden,
  212. new InvalidOperationException("Cannot rename because file " + rename + " already exists")));
  213. }
  214. Historian.UpdateLastModified(metadata);
  215. var operation = new RenameFileOperation
  216. {
  217. FileSystem = FileSystem.Name,
  218. Name = name,
  219. Rename = rename,
  220. MetadataAfterOperation = metadata
  221. };
  222. accessor.SetConfig(RavenFileNameHelper.RenameOperationConfigNameForFile(name), JsonExtensions.ToJObject(operation));
  223. accessor.PulseTransaction(); // commit rename operation config
  224. StorageOperationsTask.RenameFile(operation);
  225. }), ConcurrencyResponseException);
  226. }
  227. catch (FileNotFoundException)
  228. {
  229. log.Debug("Cannot rename a file '{0}' to '{1}' because a file was not found", name, rename);
  230. return GetEmptyMessage(HttpStatusCode.NotFound);
  231. }
  232. log.Debug("File '{0}' was renamed to '{1}'", name, rename);
  233. StartSynchronizeDestinationsInBackground();
  234. return GetMessageWithString("", HttpStatusCode.NoContent);
  235. }
  236. [HttpPut]
  237. [Route("fs/{fileSystemName}/files/{*name}")]
  238. public async Task<HttpResponseMessage> Put(string name, string uploadId = null)
  239. {
  240. try
  241. {
  242. FileSystem.MetricsCounters.FilesPerSecond.Mark();
  243. name = RavenFileNameHelper.RavenPath(name);
  244. var headers = this.GetFilteredMetadataFromHeaders(InnerHeaders);
  245. Historian.UpdateLastModified(headers);
  246. headers[Constants.CreationDate] = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffff", CultureInfo.InvariantCulture);
  247. Historian.Update(name, headers);
  248. SynchronizationTask.Cancel(name);
  249. long? size = -1;
  250. ConcurrencyAwareExecutor.Execute(() => Storage.Batch(accessor =>
  251. {
  252. AssertFileIsNotBeingSynced(name, accessor, true);
  253. StorageOperationsTask.IndicateFileToDelete(name);
  254. var contentLength = Request.Content.Headers.ContentLength;
  255. var sizeHeader = GetHeader("RavenFS-size");
  256. long sizeForParse;
  257. if (contentLength == 0 || long.TryParse(sizeHeader, out sizeForParse) == false)
  258. {
  259. size = contentLength;
  260. if (Request.Headers.TransferEncodingChunked ?? false)
  261. {
  262. size = null;
  263. }
  264. }
  265. else
  266. {
  267. size = sizeForParse;
  268. }
  269. accessor.PutFile(name, size, headers);
  270. Search.Index(name, headers);
  271. }));
  272. log.Debug("Inserted a new file '{0}' with ETag {1}", name, headers.Value<Guid>(Constants.MetadataEtagField));
  273. using (var contentStream = await Request.Content.ReadAsStreamAsync())
  274. using (var readFileToDatabase = new ReadFileToDatabase(BufferPool, Storage, contentStream, name))
  275. {
  276. await readFileToDatabase.Execute();
  277. if ( readFileToDatabase.TotalSizeRead != size )
  278. {
  279. Storage.Batch(accessor => { StorageOperationsTask.IndicateFileToDelete(name); });
  280. throw new HttpResponseException(HttpStatusCode.BadRequest);
  281. }
  282. Historian.UpdateLastModified(headers); // update with the final file size
  283. log.Debug("File '{0}' was uploaded. Starting to update file metadata and indexes", name);
  284. headers["Content-MD5"] = readFileToDatabase.FileHash;
  285. Storage.Batch(accessor => accessor.UpdateFileMetadata(name, headers));
  286. int totalSizeRead = readFileToDatabase.TotalSizeRead;
  287. headers["Content-Length"] = totalSizeRead.ToString(CultureInfo.InvariantCulture);
  288. Search.Index(name, headers);
  289. Publisher.Publish(new FileChangeNotification { Action = FileChangeAction.Add, File = FilePathTools.Cannoicalise(name) });
  290. log.Debug("Updates of '{0}' metadata and indexes were finished. New file ETag is {1}", name, headers.Value<Guid>(Constants.MetadataEtagField));
  291. StartSynchronizeDestinationsInBackground();
  292. }
  293. }
  294. catch (Exception ex)
  295. {
  296. if (uploadId != null)
  297. {
  298. Guid uploadIdentifier;
  299. if (Guid.TryParse(uploadId, out uploadIdentifier))
  300. {
  301. Publisher.Publish(new CancellationNotification { UploadId = uploadIdentifier, File = name });
  302. }
  303. }
  304. log.WarnException(string.Format("Failed to upload a file '{0}'", name), ex);
  305. var concurrencyException = ex as ConcurrencyException;
  306. if (concurrencyException != null)
  307. {
  308. throw ConcurrencyResponseException(concurrencyException);
  309. }
  310. throw;
  311. }
  312. return GetEmptyMessage(HttpStatusCode.Created);
  313. }
  314. private void StartSynchronizeDestinationsInBackground()
  315. {
  316. Task.Factory.StartNew(async () => await SynchronizationTask.SynchronizeDestinationsAsync(), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
  317. }
  318. private class ReadFileToDatabase : IDisposable
  319. {
  320. private readonly byte[] buffer;
  321. private readonly BufferPool bufferPool;
  322. private readonly string filename;
  323. private readonly Stream inputStream;
  324. private readonly ITransactionalStorage storage;
  325. private readonly IHashEncryptor md5Hasher;
  326. public int TotalSizeRead;
  327. private int pos;
  328. public ReadFileToDatabase(BufferPool bufferPool, ITransactionalStorage storage, Stream inputStream, string filename)
  329. {
  330. this.bufferPool = bufferPool;
  331. this.inputStream = inputStream;
  332. this.storage = storage;
  333. this.filename = filename;
  334. buffer = bufferPool.TakeBuffer(StorageConstants.MaxPageSize);
  335. md5Hasher = Encryptor.Current.CreateHash();
  336. }
  337. public string FileHash { get; private set; }
  338. public void Dispose()
  339. {
  340. bufferPool.ReturnBuffer(buffer);
  341. }
  342. public async Task Execute()
  343. {
  344. while (true)
  345. {
  346. var totalSizeRead = await inputStream.ReadAsync(buffer);
  347. TotalSizeRead += totalSizeRead;
  348. if (totalSizeRead == 0) // nothing left to read
  349. {
  350. storage.Batch(accessor => accessor.CompleteFileUpload(filename));
  351. FileHash = IOExtensions.GetMD5Hex(md5Hasher.TransformFinalBlock());
  352. return; // task is done
  353. }
  354. ConcurrencyAwareExecutor.Execute(() => storage.Batch(accessor =>
  355. {
  356. var hashKey = accessor.InsertPage(buffer, totalSizeRead);
  357. accessor.AssociatePage(filename, hashKey, pos, totalSizeRead);
  358. }));
  359. md5Hasher.TransformBlock(buffer, 0, totalSizeRead);
  360. pos++;
  361. }
  362. }
  363. }
  364. }
  365. }