/Accelerator/Synchronization/BlobSync.cs
C# | 287 lines | 201 code | 27 blank | 59 comment | 30 complexity | 143f926a56887daa4688f5dee5f44c07 MD5 | raw file
Possible License(s): LGPL-2.0
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.IO;
- using System.Linq;
- using System.Threading;
- using Microsoft.Synchronization;
- using Microsoft.WindowsAzure.Accelerator.Diagnostics;
- using Microsoft.WindowsAzure.StorageClient;
- using Microsoft.WindowsAzure.Diagnostics;
-
- namespace Microsoft.WindowsAzure.Accelerator.Synchronization
- {
- /// <summary>
- /// Provides data for updating a file event.
- /// </summary>
- public class UpdatingFileEventArgs : CancelEventArgs
- {
- public CloudBlob Blob;
- public String LocalPath;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="UpdatingFileEventArgs"/> class.
- /// </summary>
- /// <param name="blob">The BLOB.</param>
- /// <param name="localPath">The local path.</param>
- public UpdatingFileEventArgs(CloudBlob blob, String localPath)
- {
- Blob = blob;
- LocalPath = localPath;
- }
- }
-
-
-
- /// <summary>
- /// Performs synchronization between blob and local file system storage.
- /// </summary>
- public class BlobSync : ISyncProvider
- {
- public event UpdatingFileHandler UpdatingFile;
-
- #region | FIELDS
-
- private readonly Dictionary<String, String> _localSyncList = new Dictionary<String, String>();
- private Thread _blobSyncThread;
-
- #endregion
-
- public Boolean IsStarted { get { return _blobSyncThread != null && _blobSyncThread.ThreadState != System.Threading.ThreadState.Running; } }
- public String LocalPath { get; set; }
- public Boolean IgnoreAdditionalFiles { get; set; }
- public SyncDirectionOrder SyncDirection { get; set; }
- public Dictionary<String, String> LocalSyncList { get { return _localSyncList; } }
- public CloudStorageAccount Account { get; set; }
- public String ContainerName { get; set; }
-
- #region | CONSTRUCTORS
-
- /// <summary>
- /// Initializes a new instance of the <see cref="BlobSync"/> class.
- /// </summary>
- public BlobSync(){ }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="BlobSync"/> class.
- /// </summary>
- /// <param name="containerName">Name of the container.</param>
- /// <param name="blobDirectory">The root cloud blob directory.</param>
- /// <param name="localPath">The local path.</param>
- /// <param name="direction">The direction.</param>
- /// <param name="ignoreExisting">if set to <c>true</c> [ignore existing].</param>
- public BlobSync(String containerName, String blobDirectory, String localPath, SyncDirectionOrder direction, Boolean ignoreExisting)
- {
- ContainerName = containerName;
- LocalPath = localPath;
- SyncDirection = SyncDirection;
- IgnoreAdditionalFiles = ignoreExisting;
- SyncDirection = direction;
- }
-
- #endregion
- #region | EVENTS
-
- /// <summary>
- /// Validates this instance.
- /// </summary>
- /// <returns></returns>
- public Boolean Validate()
- {
- LogLevel.Information.TraceContent("BlobSync", "Sync Validation...", this.ToTraceString());
- return true;
- }
-
- /// <summary>
- /// Start the thread to sync blobs at intervals until Stop() is called.
- /// </summary>
- public void Start(TimeSpan interval)
- {
- _blobSyncThread = new Thread(() =>
- {
- while (true) {
- try {
- StartSync();
- }
- catch (Exception ex) {
- LogLevel.Error.TraceException("BlobSync", ex, "An exception occurred when performing blob to local storage synchroniztion");
- }
- Thread.Sleep(interval);
- }
- });
- _blobSyncThread.Start();
- }
-
- /// <summary>
- /// Stops the blob sync, terminating any in-progress transfers.
- /// </summary>
- public void Stop()
- {
- lock (_blobSyncThread)
- {
- if (IsStarted && _blobSyncThread != null && _blobSyncThread.ThreadState == System.Threading.ThreadState.Running)
- _blobSyncThread.Abort();
- }
- }
-
- /// <summary>
- /// Performs the synchronization of file system and blob storage.
- /// </summary>
- public void StartSync()
- {
- switch (SyncDirection)
- {
- case (SyncDirectionOrder.DownloadAndUpload):
- Sync(SyncDirectionOrder.Download);
- Sync(SyncDirectionOrder.Upload);
- break;
- case (SyncDirectionOrder.UploadAndDownload):
- Sync(SyncDirectionOrder.Upload);
- Sync(SyncDirectionOrder.Download);
- break;
- default:
- Sync(SyncDirection);
- break;
- }
- }
-
- /// <summary>
- /// Performs the synchronization of file system and blob storage.
- /// </summary>
- private void Sync(SyncDirectionOrder syncDirection)
- {
- if (!Validate())
- return;
-
- var blobDirectory = Account.CreateCloudBlobClient().GetBlobDirectoryReference(ContainerName);
- var directory = blobDirectory.Uri.ToString();
- var container = blobDirectory.Container;
-
- //i|
- //i| Get all of the blob in container.
- //i|
- Dictionary<String, CloudBlob> cloudBlobs = blobDirectory.ListBlobs(new BlobRequestOptions
- {
- UseFlatBlobListing = true,
- BlobListingDetails = BlobListingDetails.Metadata
- }).OfType<CloudBlob>().ToDictionary(b => b.Uri.ToString(), b => b);
-
- if (!IgnoreAdditionalFiles)
- {
- var cloudBlobNames = new HashSet<String>(cloudBlobs.Keys);
- var localBlobNames = new HashSet<String>(LocalSyncList.Keys);
-
- if (syncDirection == SyncDirectionOrder.Download)
- {
- localBlobNames.ExceptWith(cloudBlobNames);
- foreach (var uri in localBlobNames)
- {
- //i|
- //i| Delete all local files without corresponding blob files.
- //i|
- String localPath = GetLocalPath(uri, directory);
- LogLevel.Information.Trace("BlobSync", "FileDelete : Deleting Local File : {{ [Filename: '{0}'] }}.", localPath);
- File.Delete(localPath);
- LocalSyncList.Remove(uri);
- }
- }
- else if (syncDirection == SyncDirectionOrder.Upload)
- {
- cloudBlobNames.ExceptWith(localBlobNames);
- foreach (var uri in cloudBlobNames)
- {
- //i|
- //i| Delete all azure storage blogs without corresponding local files.
- //i|
- CloudBlob blob = cloudBlobs[uri];
- LogLevel.Information.Trace("BlobSync", "FileDelete : Deleting Storage Blob : {{ [BlobUri: '{0}'] }}.", uri);
- blob.DeleteIfExists();
- cloudBlobs.Remove(uri);
- }
- }
- }
-
- if (syncDirection == SyncDirectionOrder.Download)
- {
- foreach (var kvp in cloudBlobs)
- {
- var blobUri = kvp.Key;
- var blob = kvp.Value;
- if (!LocalSyncList.ContainsKey(blobUri) || LocalSyncList[blobUri] != blob.Attributes.Properties.ETag)
- {
- var localPath = GetLocalPath(blobUri, directory);
- var localDirectory = System.IO.Path.GetDirectoryName(localPath);
- var args = new UpdatingFileEventArgs(blob, localPath);
- LogLevel.Information.Trace("BlobSync", "Local File Update : {{ [LocalSyncRoot: '{0}'], [SourceBlobUri: '{1}'] }}.", localPath, blobUri);
- if (UpdatingFile != null)
- {
- UpdatingFile(this, args);
- }
- if (!args.Cancel)
- {
- Directory.CreateDirectory(localDirectory);
- using (var stream = File.Create(localPath))
- blob.DownloadToStream(stream);
- }
- LocalSyncList[blobUri] = blob.Properties.ETag;
- }
- }
- }
- else if (syncDirection == SyncDirectionOrder.Upload)
- {
- var syncList = new Dictionary<String, FileInfo>().SyncListFromDirectory(new DirectoryInfo(LocalPath), directory);
- var toSyncList = (from sl in syncList
- where !cloudBlobs.ContainsKey(sl.Key) || cloudBlobs[sl.Key].Properties.Length != sl.Value.Length
- select sl
- );
- foreach (var kvp in toSyncList)
- {
- LogLevel.Information.Trace("BlobSync", "Updating Blob : {{ [SourceFile]: '{0}', [BlobUri]: '{1}' }}.", kvp.Value.FullName, kvp.Key);
- CloudBlob blob = container.GetBlobReference(kvp.Key);
- blob.UploadFile(kvp.Value.FullName);
- }
- }
- }
-
- #endregion
- #region | UTILITY METHODS
-
- /// <summary>
- /// Gets the local path.
- /// </summary>
- /// <param name="uri">The URI.</param>
- /// <param name="syncDirectory">The sync directory.</param>
- /// <returns></returns>
- private String GetLocalPath(String uri, String syncDirectory)
- {
- return System.IO.Path.Combine(LocalPath, uri.Substring(syncDirectory.Length).SetPathChar('\\'));
- }
- #endregion
- }
-
- /// <summary>
- /// Delegate for handling the file update event.
- /// </summary>
- public delegate void UpdatingFileHandler(Object sender, UpdatingFileEventArgs args);
-
- /// <summary>
- /// Sync extension methods.
- /// </summary>
- public static class SyncExtensions
- {
- public static Dictionary<String, FileInfo> SyncListFromDirectory(this Dictionary<String, FileInfo> syncList, DirectoryInfo directory, String syncUri)
- {
- foreach (var subdir in directory.GetDirectories())
- {
- String suburi = String.Format("{0}/{1}", syncUri.TrimEnd(' ', '\\', '/'), subdir.Name);
- syncList.SyncListFromDirectory(subdir, suburi);
- }
- foreach (var file in directory.GetFiles())
- {
- String fileuri = String.Format("{0}/{1}", syncUri.TrimEnd(' ', '\\', '/'), file.Name);
- syncList[fileuri] = file;
- }
- return syncList;
- }
- }
- }