PageRenderTime 322ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/Source/Lokad.Cloud.Framework/Storage/StorageExtensions.cs

http://github.com/cdrnet/Lokad.Cloud
C# | 245 lines | 151 code | 33 blank | 61 comment | 3 complexity | 9c85748c79f0239be25a39ecd84e0a37 MD5 | raw file
  1. #region Copyright (c) Lokad 2009-2010
  2. // This code is released under the terms of the new BSD licence.
  3. // URL: http://www.lokad.com/
  4. #endregion
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Runtime.Serialization;
  9. using System.Text.RegularExpressions;
  10. using System.Threading;
  11. // ReSharper disable UnusedMember.Global
  12. // ReSharper disable UnusedMethodReturnValue.Global
  13. namespace Lokad.Cloud.Storage
  14. {
  15. /// <summary>Helper extensions methods for storage providers.</summary>
  16. public static class StorageExtensions
  17. {
  18. static readonly Random _rand = new Random();
  19. public static void AtomicUpdate<T>(this IBlobStorageProvider provider, string containerName, string blobName, Func<Maybe<T>, Result<T>> updater, out Result<T> result)
  20. {
  21. Result<T> tmpResult = null;
  22. RetryUpdate(() => provider.UpdateIfNotModified(containerName, blobName, updater, out tmpResult));
  23. result = tmpResult;
  24. }
  25. public static void AtomicUpdate<T>(this IBlobStorageProvider provider, string containerName, string blobName, Func<Maybe<T>, T> updater, out T result)
  26. {
  27. T tmpResult = default(T);
  28. RetryUpdate(() => provider.UpdateIfNotModified(containerName, blobName, updater, out tmpResult));
  29. result = tmpResult;
  30. }
  31. /// <summary>Retry an update method until it succeeds. Timing
  32. /// increases to avoid overstressing the storage for nothing.
  33. /// Maximal delay is set to 10 seconds.</summary>
  34. static void RetryUpdate(Func<bool> func)
  35. {
  36. // HACK: hard-coded constant, the whole counter system have to be perfected.
  37. const int step = 10;
  38. const int maxDelayInMilliseconds = 10000;
  39. int retryAttempts = 0;
  40. while (!func())
  41. {
  42. retryAttempts++;
  43. var sleepTime = _rand.Next(Math.Max(retryAttempts * retryAttempts * step, maxDelayInMilliseconds)).Milliseconds();
  44. Thread.Sleep(sleepTime);
  45. }
  46. }
  47. public static void AtomicUpdate<T>(this IBlobStorageProvider provider, BlobName<T> name, Func<Maybe<T>, Result<T>> updater, out Result<T> result)
  48. {
  49. AtomicUpdate(provider, name.ContainerName, name.ToString(), updater, out result);
  50. }
  51. public static void AtomicUpdate<T>(this IBlobStorageProvider provider, BlobName<T> name, Func<Maybe<T>, T> updater, out T result)
  52. {
  53. AtomicUpdate(provider, name.ContainerName, name.ToString(), updater, out result);
  54. }
  55. public static bool DeleteBlob<T>(this IBlobStorageProvider provider, BlobName<T> fullName)
  56. {
  57. return provider.DeleteBlob(fullName.ContainerName, fullName.ToString());
  58. }
  59. public static Maybe<T> GetBlob<T>(this IBlobStorageProvider provider, BlobName<T> name)
  60. {
  61. return provider.GetBlob<T>(name.ContainerName, name.ToString());
  62. }
  63. public static Maybe<T> GetBlob<T>(this IBlobStorageProvider provider, BlobName<T> name, out string etag)
  64. {
  65. return provider.GetBlob<T>(name.ContainerName, name.ToString(), out etag);
  66. }
  67. public static string GetBlobEtag<T>(this IBlobStorageProvider provider, BlobName<T> name)
  68. {
  69. return provider.GetBlobEtag(name.ContainerName, name.ToString());
  70. }
  71. /// <summary>Gets the corresponding object. If the deserialization fails
  72. /// just delete the existing copy.</summary>
  73. public static Maybe<T> GetBlobOrDelete<T>(this IBlobStorageProvider provider, string containerName, string blobName)
  74. {
  75. try
  76. {
  77. return provider.GetBlob<T>(containerName, blobName);
  78. }
  79. catch (SerializationException)
  80. {
  81. provider.DeleteBlob(containerName, blobName);
  82. return Maybe<T>.Empty;
  83. }
  84. catch (InvalidCastException)
  85. {
  86. provider.DeleteBlob(containerName, blobName);
  87. return Maybe<T>.Empty;
  88. }
  89. }
  90. /// <summary>Gets the corresponding object. If the deserialization fails
  91. /// just delete the existing copy.</summary>
  92. public static Maybe<T> GetBlobOrDelete<T>(this IBlobStorageProvider provider, BlobName<T> name)
  93. {
  94. return provider.GetBlobOrDelete<T>(name.ContainerName, name.ToString());
  95. }
  96. public static void PutBlob<T>(this IBlobStorageProvider provider, BlobName<T> name, T item)
  97. {
  98. provider.PutBlob(name.ContainerName, name.ToString(), item);
  99. }
  100. public static bool PutBlob<T>(this IBlobStorageProvider provider, BlobName<T> name, T item, bool overwrite)
  101. {
  102. return provider.PutBlob(name.ContainerName, name.ToString(), item, overwrite);
  103. }
  104. /// <summary>Push the blob only if etag is matching the etag of the blob in BlobStorage</summary>
  105. public static bool PutBlob<T>(this IBlobStorageProvider provider, BlobName<T> name, T item, string etag)
  106. {
  107. return provider.PutBlob(name.ContainerName, name.ToString(), item, etag);
  108. }
  109. public static IEnumerable<T> List<T>(
  110. this IBlobStorageProvider provider, T prefix) where T : UntypedBlobName
  111. {
  112. return provider.List(prefix.ContainerName, prefix.ToString())
  113. .Select(rawName => UntypedBlobName.Parse<T>(rawName));
  114. }
  115. public static bool UpdateIfNotModified<T>(this IBlobStorageProvider provider,
  116. BlobName<T> name, Func<Maybe<T>, Result<T>> updater, out Result<T> result)
  117. {
  118. return provider.UpdateIfNotModified(name.ContainerName, name.ToString(), updater, out result);
  119. }
  120. public static bool UpdateIfNotModified<T>(this IBlobStorageProvider provider,
  121. BlobName<T> name, Func<Maybe<T>, T> updater, out T result)
  122. {
  123. return provider.UpdateIfNotModified(name.ContainerName, name.ToString(), updater, out result);
  124. }
  125. public static bool UpdateIfNotModified<T>(this IBlobStorageProvider provider,
  126. BlobName<T> name, Func<Maybe<T>, Result<T>> updater)
  127. {
  128. return provider.UpdateIfNotModified(name.ContainerName, name.ToString(), updater);
  129. }
  130. public static bool UpdateIfNotModified<T>(this IBlobStorageProvider provider,
  131. BlobName<T> name, Func<Maybe<T>, T> updater)
  132. {
  133. return provider.UpdateIfNotModified(name.ContainerName, name.ToString(), updater);
  134. }
  135. /// <summary>Gets messages from a queue with a visibility timeout of 2 hours and a maximum of 50 processing trials.</summary>
  136. /// <typeparam name="T">Type of the messages.</typeparam>
  137. /// <param name="queueName">Identifier of the queue to be pulled.</param>
  138. /// <param name="count">Maximal number of messages to be retrieved.</param>
  139. /// <returns>Enumeration of messages, possibly empty.</returns>
  140. public static IEnumerable<T> Get<T>(this IQueueStorageProvider provider, string queueName, int count)
  141. {
  142. return provider.Get<T>(queueName, count, new TimeSpan(2, 0, 0), 5);
  143. }
  144. /// <summary>Gets messages from a queue with a visibility timeout of 2 hours.</summary>
  145. /// <typeparam name="T">Type of the messages.</typeparam>
  146. /// <param name="queueName">Identifier of the queue to be pulled.</param>
  147. /// <param name="count">Maximal number of messages to be retrieved.</param>
  148. /// <param name="maxProcessingTrials">
  149. /// Maximum number of message processing trials, before the message is considered as
  150. /// being poisonous, removed from the queue and persisted to the 'failing-messages' store.
  151. /// </param>
  152. /// <returns>Enumeration of messages, possibly empty.</returns>
  153. public static IEnumerable<T> Get<T>(this IQueueStorageProvider provider, string queueName, int count, int maxProcessingTrials)
  154. {
  155. return provider.Get<T>(queueName, count, new TimeSpan(2, 0, 0), maxProcessingTrials);
  156. }
  157. /// <summary>Gets the specified cloud entity if it exists.</summary>
  158. /// <typeparam name="T"></typeparam>
  159. public static Maybe<CloudEntity<T>> Get<T>(this ITableStorageProvider provider, string tableName, string partitionName, string rowKey)
  160. {
  161. return provider.Get<T>(tableName, partitionName, new[] { rowKey }).FirstOrEmpty();
  162. }
  163. /// <summary>Gets a strong typed wrapper around the table storage provider.</summary>
  164. public static CloudTable<T> GetTable<T>(this ITableStorageProvider provider, string tableName)
  165. {
  166. return new CloudTable<T>(provider, tableName);
  167. }
  168. /// <summary>Updates a collection of existing entities into the table storage.</summary>
  169. /// <remarks>
  170. /// <para>The call is expected to fail on the first non-existing entity.
  171. /// Results are not garanteed if one or several entities do not exist already.
  172. /// </para>
  173. /// <para>The call is also expected to fail if one or several entities have
  174. /// changed remotely in the meantime. Use the overloaded method with the additional
  175. /// force parameter to change this behavior if needed.
  176. /// </para>
  177. /// <para>There is no upper limit on the number of entities provided through
  178. /// the enumeration. The implementations are expected to lazily iterates
  179. /// and to create batch requests as the move forward.
  180. /// </para>
  181. /// <para>Idempotence of the implementation is required.</para>
  182. /// </remarks>
  183. /// <exception cref="InvalidOperationException"> thrown if the table does not exist
  184. /// or an non-existing entity has been encountered.</exception>
  185. public static void Update<T>(this ITableStorageProvider provider, string tableName, IEnumerable<CloudEntity<T>> entities)
  186. {
  187. provider.Update(tableName, entities, false);
  188. }
  189. /// <summary>Deletes a collection of entities.</summary>
  190. /// <remarks>
  191. /// <para>
  192. /// The implementation is expected to lazily iterate through all row keys
  193. /// and send batch deletion request to the underlying storage.</para>
  194. /// <para>Idempotence of the method is required.</para>
  195. /// <para>The method should not fail if the table does not exist.</para>
  196. /// <para>The call is expected to fail if one or several entities have
  197. /// changed remotely in the meantime. Use the overloaded method with the additional
  198. /// force parameter to change this behavior if needed.
  199. /// </para>
  200. /// </remarks>
  201. public static void Delete<T>(this ITableStorageProvider provider, string tableName, IEnumerable<CloudEntity<T>> entities)
  202. {
  203. provider.Delete(tableName, entities, false);
  204. }
  205. /// <summary>Checks that containerName is a valid DNS name, as requested by Azure</summary>
  206. public static bool IsContainerNameValid(string containerName)
  207. {
  208. return (Regex.IsMatch(containerName, @"(^([a-z]|\d))((-([a-z]|\d)|([a-z]|\d))+)$")
  209. && (3 <= containerName.Length) && (containerName.Length <= 63));
  210. }
  211. }
  212. }