PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/Accelerator/Synchronization/BlobSync.cs

https://bitbucket.org/zgramana/azure-accelerators-project
C# | 287 lines | 201 code | 27 blank | 59 comment | 30 complexity | 143f926a56887daa4688f5dee5f44c07 MD5 | raw file
Possible License(s): LGPL-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading;
  7. using Microsoft.Synchronization;
  8. using Microsoft.WindowsAzure.Accelerator.Diagnostics;
  9. using Microsoft.WindowsAzure.StorageClient;
  10. using Microsoft.WindowsAzure.Diagnostics;
  11. namespace Microsoft.WindowsAzure.Accelerator.Synchronization
  12. {
  13. /// <summary>
  14. /// Provides data for updating a file event.
  15. /// </summary>
  16. public class UpdatingFileEventArgs : CancelEventArgs
  17. {
  18. public CloudBlob Blob;
  19. public String LocalPath;
  20. /// <summary>
  21. /// Initializes a new instance of the <see cref="UpdatingFileEventArgs"/> class.
  22. /// </summary>
  23. /// <param name="blob">The BLOB.</param>
  24. /// <param name="localPath">The local path.</param>
  25. public UpdatingFileEventArgs(CloudBlob blob, String localPath)
  26. {
  27. Blob = blob;
  28. LocalPath = localPath;
  29. }
  30. }
  31. /// <summary>
  32. /// Performs synchronization between blob and local file system storage.
  33. /// </summary>
  34. public class BlobSync : ISyncProvider
  35. {
  36. public event UpdatingFileHandler UpdatingFile;
  37. #region | FIELDS
  38. private readonly Dictionary<String, String> _localSyncList = new Dictionary<String, String>();
  39. private Thread _blobSyncThread;
  40. #endregion
  41. public Boolean IsStarted { get { return _blobSyncThread != null && _blobSyncThread.ThreadState != System.Threading.ThreadState.Running; } }
  42. public String LocalPath { get; set; }
  43. public Boolean IgnoreAdditionalFiles { get; set; }
  44. public SyncDirectionOrder SyncDirection { get; set; }
  45. public Dictionary<String, String> LocalSyncList { get { return _localSyncList; } }
  46. public CloudStorageAccount Account { get; set; }
  47. public String ContainerName { get; set; }
  48. #region | CONSTRUCTORS
  49. /// <summary>
  50. /// Initializes a new instance of the <see cref="BlobSync"/> class.
  51. /// </summary>
  52. public BlobSync(){ }
  53. /// <summary>
  54. /// Initializes a new instance of the <see cref="BlobSync"/> class.
  55. /// </summary>
  56. /// <param name="containerName">Name of the container.</param>
  57. /// <param name="blobDirectory">The root cloud blob directory.</param>
  58. /// <param name="localPath">The local path.</param>
  59. /// <param name="direction">The direction.</param>
  60. /// <param name="ignoreExisting">if set to <c>true</c> [ignore existing].</param>
  61. public BlobSync(String containerName, String blobDirectory, String localPath, SyncDirectionOrder direction, Boolean ignoreExisting)
  62. {
  63. ContainerName = containerName;
  64. LocalPath = localPath;
  65. SyncDirection = SyncDirection;
  66. IgnoreAdditionalFiles = ignoreExisting;
  67. SyncDirection = direction;
  68. }
  69. #endregion
  70. #region | EVENTS
  71. /// <summary>
  72. /// Validates this instance.
  73. /// </summary>
  74. /// <returns></returns>
  75. public Boolean Validate()
  76. {
  77. LogLevel.Information.TraceContent("BlobSync", "Sync Validation...", this.ToTraceString());
  78. return true;
  79. }
  80. /// <summary>
  81. /// Start the thread to sync blobs at intervals until Stop() is called.
  82. /// </summary>
  83. public void Start(TimeSpan interval)
  84. {
  85. _blobSyncThread = new Thread(() =>
  86. {
  87. while (true) {
  88. try {
  89. StartSync();
  90. }
  91. catch (Exception ex) {
  92. LogLevel.Error.TraceException("BlobSync", ex, "An exception occurred when performing blob to local storage synchroniztion");
  93. }
  94. Thread.Sleep(interval);
  95. }
  96. });
  97. _blobSyncThread.Start();
  98. }
  99. /// <summary>
  100. /// Stops the blob sync, terminating any in-progress transfers.
  101. /// </summary>
  102. public void Stop()
  103. {
  104. lock (_blobSyncThread)
  105. {
  106. if (IsStarted && _blobSyncThread != null && _blobSyncThread.ThreadState == System.Threading.ThreadState.Running)
  107. _blobSyncThread.Abort();
  108. }
  109. }
  110. /// <summary>
  111. /// Performs the synchronization of file system and blob storage.
  112. /// </summary>
  113. public void StartSync()
  114. {
  115. switch (SyncDirection)
  116. {
  117. case (SyncDirectionOrder.DownloadAndUpload):
  118. Sync(SyncDirectionOrder.Download);
  119. Sync(SyncDirectionOrder.Upload);
  120. break;
  121. case (SyncDirectionOrder.UploadAndDownload):
  122. Sync(SyncDirectionOrder.Upload);
  123. Sync(SyncDirectionOrder.Download);
  124. break;
  125. default:
  126. Sync(SyncDirection);
  127. break;
  128. }
  129. }
  130. /// <summary>
  131. /// Performs the synchronization of file system and blob storage.
  132. /// </summary>
  133. private void Sync(SyncDirectionOrder syncDirection)
  134. {
  135. if (!Validate())
  136. return;
  137. var blobDirectory = Account.CreateCloudBlobClient().GetBlobDirectoryReference(ContainerName);
  138. var directory = blobDirectory.Uri.ToString();
  139. var container = blobDirectory.Container;
  140. //i|
  141. //i| Get all of the blob in container.
  142. //i|
  143. Dictionary<String, CloudBlob> cloudBlobs = blobDirectory.ListBlobs(new BlobRequestOptions
  144. {
  145. UseFlatBlobListing = true,
  146. BlobListingDetails = BlobListingDetails.Metadata
  147. }).OfType<CloudBlob>().ToDictionary(b => b.Uri.ToString(), b => b);
  148. if (!IgnoreAdditionalFiles)
  149. {
  150. var cloudBlobNames = new HashSet<String>(cloudBlobs.Keys);
  151. var localBlobNames = new HashSet<String>(LocalSyncList.Keys);
  152. if (syncDirection == SyncDirectionOrder.Download)
  153. {
  154. localBlobNames.ExceptWith(cloudBlobNames);
  155. foreach (var uri in localBlobNames)
  156. {
  157. //i|
  158. //i| Delete all local files without corresponding blob files.
  159. //i|
  160. String localPath = GetLocalPath(uri, directory);
  161. LogLevel.Information.Trace("BlobSync", "FileDelete : Deleting Local File : {{ [Filename: '{0}'] }}.", localPath);
  162. File.Delete(localPath);
  163. LocalSyncList.Remove(uri);
  164. }
  165. }
  166. else if (syncDirection == SyncDirectionOrder.Upload)
  167. {
  168. cloudBlobNames.ExceptWith(localBlobNames);
  169. foreach (var uri in cloudBlobNames)
  170. {
  171. //i|
  172. //i| Delete all azure storage blogs without corresponding local files.
  173. //i|
  174. CloudBlob blob = cloudBlobs[uri];
  175. LogLevel.Information.Trace("BlobSync", "FileDelete : Deleting Storage Blob : {{ [BlobUri: '{0}'] }}.", uri);
  176. blob.DeleteIfExists();
  177. cloudBlobs.Remove(uri);
  178. }
  179. }
  180. }
  181. if (syncDirection == SyncDirectionOrder.Download)
  182. {
  183. foreach (var kvp in cloudBlobs)
  184. {
  185. var blobUri = kvp.Key;
  186. var blob = kvp.Value;
  187. if (!LocalSyncList.ContainsKey(blobUri) || LocalSyncList[blobUri] != blob.Attributes.Properties.ETag)
  188. {
  189. var localPath = GetLocalPath(blobUri, directory);
  190. var localDirectory = System.IO.Path.GetDirectoryName(localPath);
  191. var args = new UpdatingFileEventArgs(blob, localPath);
  192. LogLevel.Information.Trace("BlobSync", "Local File Update : {{ [LocalSyncRoot: '{0}'], [SourceBlobUri: '{1}'] }}.", localPath, blobUri);
  193. if (UpdatingFile != null)
  194. {
  195. UpdatingFile(this, args);
  196. }
  197. if (!args.Cancel)
  198. {
  199. Directory.CreateDirectory(localDirectory);
  200. using (var stream = File.Create(localPath))
  201. blob.DownloadToStream(stream);
  202. }
  203. LocalSyncList[blobUri] = blob.Properties.ETag;
  204. }
  205. }
  206. }
  207. else if (syncDirection == SyncDirectionOrder.Upload)
  208. {
  209. var syncList = new Dictionary<String, FileInfo>().SyncListFromDirectory(new DirectoryInfo(LocalPath), directory);
  210. var toSyncList = (from sl in syncList
  211. where !cloudBlobs.ContainsKey(sl.Key) || cloudBlobs[sl.Key].Properties.Length != sl.Value.Length
  212. select sl
  213. );
  214. foreach (var kvp in toSyncList)
  215. {
  216. LogLevel.Information.Trace("BlobSync", "Updating Blob : {{ [SourceFile]: '{0}', [BlobUri]: '{1}' }}.", kvp.Value.FullName, kvp.Key);
  217. CloudBlob blob = container.GetBlobReference(kvp.Key);
  218. blob.UploadFile(kvp.Value.FullName);
  219. }
  220. }
  221. }
  222. #endregion
  223. #region | UTILITY METHODS
  224. /// <summary>
  225. /// Gets the local path.
  226. /// </summary>
  227. /// <param name="uri">The URI.</param>
  228. /// <param name="syncDirectory">The sync directory.</param>
  229. /// <returns></returns>
  230. private String GetLocalPath(String uri, String syncDirectory)
  231. {
  232. return System.IO.Path.Combine(LocalPath, uri.Substring(syncDirectory.Length).SetPathChar('\\'));
  233. }
  234. #endregion
  235. }
  236. /// <summary>
  237. /// Delegate for handling the file update event.
  238. /// </summary>
  239. public delegate void UpdatingFileHandler(Object sender, UpdatingFileEventArgs args);
  240. /// <summary>
  241. /// Sync extension methods.
  242. /// </summary>
  243. public static class SyncExtensions
  244. {
  245. public static Dictionary<String, FileInfo> SyncListFromDirectory(this Dictionary<String, FileInfo> syncList, DirectoryInfo directory, String syncUri)
  246. {
  247. foreach (var subdir in directory.GetDirectories())
  248. {
  249. String suburi = String.Format("{0}/{1}", syncUri.TrimEnd(' ', '\\', '/'), subdir.Name);
  250. syncList.SyncListFromDirectory(subdir, suburi);
  251. }
  252. foreach (var file in directory.GetFiles())
  253. {
  254. String fileuri = String.Format("{0}/{1}", syncUri.TrimEnd(' ', '\\', '/'), file.Name);
  255. syncList[fileuri] = file;
  256. }
  257. return syncList;
  258. }
  259. }
  260. }