Modifications to enable Sync Pausing for all operations
[pithos-ms-client] / trunk / Pithos.Core / Agents / Downloader.cs
index 8e3416c..4075774 100644 (file)
@@ -1,8 +1,11 @@
 using System;
+using System.Collections.Generic;
 using System.ComponentModel.Composition;
 using System.Diagnostics.Contracts;
 using System.IO;
+using System.Linq;
 using System.Reflection;
+using System.Threading;
 using System.Threading.Tasks;
 using Pithos.Interfaces;
 using Pithos.Network;
@@ -21,8 +24,18 @@ namespace Pithos.Core.Agents
         
         public IStatusNotification StatusNotification { get; set; }
 
+/*
+        private CancellationTokenSource _cts=new CancellationTokenSource();
+
+        public void SignalStop()
+        {
+            _cts.Cancel();
+        }
+*/
+
+
         //Download a file.
-        public async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath)
+        public async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath,CancellationToken cancellationToken)
         {
             if (accountInfo == null)
                 throw new ArgumentNullException("accountInfo");
@@ -37,85 +50,93 @@ namespace Pithos.Core.Agents
             if (!Path.IsPathRooted(filePath))
                 throw new ArgumentException("The filePath must be rooted", "filePath");
             Contract.EndContractBlock();
+                using (ThreadContext.Stacks["Operation"].Push("DownloadCloudFile"))
+                {
+                   // var cancellationToken=_cts.Token;//  .ThrowIfCancellationRequested();
 
-            using (ThreadContext.Stacks["Operation"].Push("DownloadCloudFile"))
-            {
+                    cancellationToken.ThrowIfCancellationRequested();
+                    await UnpauseEvent.WaitAsync();
 
-                var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
-                var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
 
-                var url = relativeUrl.ToString();
-                if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
-                    return;
+                    var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
+                    var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
 
+                    var url = relativeUrl.ToString();
+                    if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
+                        return;
 
-                //Are we already downloading or uploading the file? 
-                using (var gate = NetworkGate.Acquire(localPath, NetworkOperation.Downloading))
-                {
-                    if (gate.Failed)
+                    if (!Selectives.IsSelected(cloudFile))
                         return;
 
-                    var client = new CloudFilesClient(accountInfo);
-                    var account = cloudFile.Account;
-                    var container = cloudFile.Container;
 
-                    if (cloudFile.IsDirectory)
+                    //Are we already downloading or uploading the file? 
+                    using (var gate = NetworkGate.Acquire(localPath, NetworkOperation.Downloading))
                     {
-                        if (!Directory.Exists(localPath))
-                            try
-                            {
-                                Directory.CreateDirectory(localPath);
-                                if (Log.IsDebugEnabled)
-                                    Log.DebugFormat("Created Directory [{0}]", localPath);
-                            }
-                            catch (IOException)
-                            {
-                                var localInfo = new FileInfo(localPath);
-                                if (localInfo.Exists && localInfo.Length == 0)
+                        if (gate.Failed)
+                            return;
+
+                        var client = new CloudFilesClient(accountInfo);
+                        var account = cloudFile.Account;
+                        var container = cloudFile.Container;
+
+                        if (cloudFile.IsDirectory)
+                        {
+                            if (!Directory.Exists(localPath))
+                                try
                                 {
-                                    Log.WarnFormat("Malformed directory object detected for [{0}]", localPath);
-                                    localInfo.Delete();
                                     Directory.CreateDirectory(localPath);
                                     if (Log.IsDebugEnabled)
                                         Log.DebugFormat("Created Directory [{0}]", localPath);
                                 }
-                            }
-                    }
-                    else
-                    {
-                        var isChanged = IsObjectChanged(cloudFile, localPath);
-                        if (isChanged)
+                                catch (IOException)
+                                {
+                                    var localInfo = new FileInfo(localPath);
+                                    if (localInfo.Exists && localInfo.Length == 0)
+                                    {
+                                        Log.WarnFormat("Malformed directory object detected for [{0}]", localPath);
+                                        localInfo.Delete();
+                                        Directory.CreateDirectory(localPath);
+                                        if (Log.IsDebugEnabled)
+                                            Log.DebugFormat("Created Directory [{0}]", localPath);
+                                    }
+                                }
+                        }
+                        else
                         {
-                            //Retrieve the hashmap from the server
-                            var serverHash = await client.GetHashMap(account, container, url);
-                            //If it's a small file
-                            if (serverHash.Hashes.Count == 1)
-                                //Download it in one go
-                                await
-                                    DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath);
-                            //Otherwise download it block by block
-                            else
-                                await
-                                    DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath,
-                                                       serverHash);
-
-                            if (!cloudFile.IsWritable(accountInfo.UserName))
+                            var isChanged = IsObjectChanged(cloudFile, localPath);
+                            if (isChanged)
                             {
-                                var attributes = File.GetAttributes(localPath);
-                                File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);
+                                //Retrieve the hashmap from the server
+                                var serverHash = await client.GetHashMap(account, container, url);
+                                //If it's a small file
+                                if (serverHash.Hashes.Count == 1)
+                                    //Download it in one go
+                                    await
+                                        DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath,cancellationToken);
+                                    //Otherwise download it block by block
+                                else
+                                    await
+                                        DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath,
+                                                           serverHash,cancellationToken);
+
+                                if (!cloudFile.IsWritable(accountInfo.UserName))
+                                {
+                                    var attributes = File.GetAttributes(localPath);
+                                    File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);
+                                }
                             }
                         }
-                    }
 
-                    //Now we can store the object's metadata without worrying about ghost status entries
-                    StatusKeeper.StoreInfo(localPath, cloudFile);
+                        //Now we can store the object's metadata without worrying about ghost status entries
+                        StatusKeeper.StoreInfo(localPath, cloudFile);
 
+                    }
                 }
-            }
+           
         }
 
         //Download a file asynchronously using blocks
-        public async Task DownloadWithBlocks(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, TreeHash serverHash)
+        public async Task DownloadWithBlocks(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, TreeHash serverHash, CancellationToken cancellationToken)
         {
             if (client == null)
                 throw new ArgumentNullException("client");
@@ -133,6 +154,9 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");
             Contract.EndContractBlock();
 
+            cancellationToken.ThrowIfCancellationRequested();
+            await UnpauseEvent.WaitAsync();
+
             var fileAgent = GetFileAgent(accountInfo);
             var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
 
@@ -140,9 +164,10 @@ namespace Pithos.Core.Agents
             var relativePath = relativeUrl.RelativeUriToFilePath();
             var blockUpdater = new BlockUpdater(fileAgent.CachePath, localPath, relativePath, serverHash);
 
-
             StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Calculating hashmap for {0} before download", Path.GetFileName(localPath)));
             //Calculate the file's treehash
+
+            //TODO: Should pass cancellation token here
             var treeHash = await Signature.CalculateTreeHashAsync(localPath, serverHash.BlockSize, serverHash.BlockHash, 2);
 
             //And compare it with the server's hash
@@ -151,6 +176,9 @@ namespace Pithos.Core.Agents
             ReportDownloadProgress(Path.GetFileName(localPath), 0, upHashes.Length, cloudFile.Bytes);
             for (int i = 0; i < upHashes.Length; i++)
             {
+                cancellationToken.ThrowIfCancellationRequested();
+                await UnpauseEvent.WaitAsync();
+
                 //For every non-matching hash
                 var upHash = upHashes[i];
                 if (!localHashes.ContainsKey(upHash))
@@ -169,8 +197,9 @@ namespace Pithos.Core.Agents
                     if (i < upHashes.Length - 1)
                         end = ((i + 1) * serverHash.BlockSize);
 
+                    //TODO: Pass token here
                     //Download the missing block
-                    var block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end);
+                    var block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end,cancellationToken);
 
                     //and store it
                     blockUpdater.StoreBlock(i, block);
@@ -193,7 +222,7 @@ namespace Pithos.Core.Agents
         }
 
         //Download a small file with a single GET operation
-        private async Task DownloadEntireFileAsync(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath)
+        private async Task DownloadEntireFileAsync(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, CancellationToken cancellationToken)
         {
             if (client == null)
                 throw new ArgumentNullException("client");
@@ -209,6 +238,9 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");
             Contract.EndContractBlock();
 
+            cancellationToken.ThrowIfCancellationRequested();
+            await UnpauseEvent.WaitAsync();
+
             var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
             StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Downloading {0}", Path.GetFileName(localPath)));
             StatusNotification.Notify(new CloudNotification { Data = cloudFile });
@@ -223,8 +255,10 @@ namespace Pithos.Core.Agents
             if (!Directory.Exists(tempFolder))
                 Directory.CreateDirectory(tempFolder);
 
+            //TODO: Should pass the token here
+
             //Download the object to the temporary location
-            await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath);
+            await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath,cancellationToken);
 
             //Create the local folder if it doesn't exist (necessary for shared objects)
             var localFolder = Path.GetDirectoryName(localPath);
@@ -293,6 +327,9 @@ namespace Pithos.Core.Agents
             return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);
         }
 
+        [Import]
+        public Selectives Selectives { get; set; }
 
+        public AsyncManualResetEvent UnpauseEvent { get; set; }
     }
 }