Convert all url usages to use the Uri class instead of raw strings.
[pithos-ms-client] / trunk / Pithos.Core / Agents / Downloader.cs
index 980743e..584141a 100644 (file)
@@ -1,9 +1,7 @@
 using System;\r
-using System.Collections.Generic;\r
 using System.ComponentModel.Composition;\r
 using System.Diagnostics.Contracts;\r
 using System.IO;\r
-using System.Linq;\r
 using System.Reflection;\r
 using System.Threading;\r
 using System.Threading.Tasks;\r
@@ -21,6 +19,8 @@ namespace Pithos.Core.Agents
         [Import]\r
         private IStatusKeeper StatusKeeper { get; set; }\r
 \r
+        [Import]\r
+        private IPithosSettings Settings { get; set; }\r
         \r
         public IStatusNotification StatusNotification { get; set; }\r
 \r
@@ -35,7 +35,7 @@ namespace Pithos.Core.Agents
 \r
 \r
         //Download a file.\r
-        public async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath,TreeHash localTreeHash,CancellationToken cancellationToken)\r
+        public async Task<TreeHash> DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath,CancellationToken cancellationToken)\r
         {\r
             if (accountInfo == null)\r
                 throw new ArgumentNullException("accountInfo");\r
@@ -43,7 +43,9 @@ namespace Pithos.Core.Agents
                 throw new ArgumentNullException("cloudFile");\r
             if (String.IsNullOrWhiteSpace(cloudFile.Account))\r
                 throw new ArgumentNullException("cloudFile");\r
-            if (String.IsNullOrWhiteSpace(cloudFile.Container))\r
+            if (cloudFile.Container==null)\r
+                throw new ArgumentNullException("cloudFile");\r
+            if (cloudFile.Container.IsAbsoluteUri)\r
                 throw new ArgumentNullException("cloudFile");\r
             if (String.IsNullOrWhiteSpace(filePath))\r
                 throw new ArgumentNullException("filePath");\r
@@ -54,26 +56,43 @@ namespace Pithos.Core.Agents
                 {\r
                    // var cancellationToken=_cts.Token;//  .ThrowIfCancellationRequested();\r
 \r
-                    if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken))\r
-                        return;\r
+                    //The file's treehash after download completes. For directories, the treehash is always the empty hash\r
+                    var finalHash = TreeHash.Empty;\r
+\r
+                    if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken).ConfigureAwait(false))\r
+                        return finalHash;\r
 \r
+                    var fileName = Path.GetFileName(filePath);\r
 \r
-                    var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);\r
-                    var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);\r
+                    var info = FileInfoExtensions.FromPath(filePath).WithProperCapitalization();\r
+\r
+                    TreeHash localTreeHash;\r
+\r
+                    using (StatusNotification.GetNotifier("Hashing for Download {0}", "Hashed for Download {0}", fileName))\r
+                    {\r
+                        var state = StatusKeeper.GetStateByFilePath(filePath);\r
+                        var progress = new Progress<double>(d =>\r
+                            StatusNotification.Notify(new StatusNotification(String.Format("Hashing for Download {0} of {1}", d, fileName))));\r
+\r
+                        localTreeHash = StatusAgent.CalculateTreeHash(info, accountInfo, state, Settings.HashingParallelism, cancellationToken, progress);\r
+                    }\r
+                    \r
+                    var localPath = info.FullName;\r
+                    var relativeUrl = cloudFile.Name;\r
 \r
                     var url = relativeUrl.ToString();\r
-                    if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))\r
-                        return;\r
+                    if (url.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))\r
+                        return finalHash;\r
 \r
                     if (!Selectives.IsSelected(accountInfo,cloudFile))\r
-                        return;\r
+                        return finalHash;\r
 \r
 \r
                     //Are we already downloading or uploading the file? \r
                     using (var gate = NetworkGate.Acquire(localPath, NetworkOperation.Downloading))\r
                     {\r
                         if (gate.Failed)\r
-                            return;\r
+                            return finalHash;\r
 \r
                         var client = new CloudFilesClient(accountInfo);\r
                         var account = cloudFile.Account;\r
@@ -103,11 +122,11 @@ namespace Pithos.Core.Agents
                         }\r
                         else\r
                         {\r
-                            var isChanged = IsObjectChanged(cloudFile, localPath);\r
+                            var isChanged = IsObjectChanged(cloudFile, localPath,localTreeHash);\r
                             if (isChanged)\r
                             {\r
                                 //Retrieve the hashmap from the server\r
-                                var serverHash = await client.GetHashMap(account, container, url);\r
+                                var serverHash = await client.GetHashMap(account, container, relativeUrl).ConfigureAwait(false);\r
                                 //If it's a small file\r
                                 if (serverHash.Hashes.Count == 1)\r
                                     //Download it in one go\r
@@ -124,13 +143,18 @@ namespace Pithos.Core.Agents
                                     var attributes = File.GetAttributes(localPath);\r
                                     File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);\r
                                 }\r
+\r
+                                //Once download completes, the final hash will be equal to the server hash\r
+                                finalHash = serverHash;\r
+\r
                             }\r
                         }\r
 \r
                         //Now we can store the object's metadata without worrying about ghost status entries\r
-                        StatusKeeper.StoreInfo(localPath, cloudFile);\r
+                        StatusKeeper.StoreInfo(localPath, cloudFile,finalHash);\r
 \r
                     }\r
+                    return finalHash;\r
                 }\r
            \r
         }\r
@@ -154,7 +178,7 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");\r
             Contract.EndContractBlock();\r
 \r
-            if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken))\r
+            if (await WaitOrAbort(accountInfo, cloudFile, cancellationToken).ConfigureAwait(false))\r
                 return;\r
 \r
             var fileAgent = GetFileAgent(accountInfo);\r
@@ -167,8 +191,11 @@ namespace Pithos.Core.Agents
             StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Calculating hashmap for {0} before download", Path.GetFileName(localPath)));\r
             //Calculate the file's treehash\r
 \r
-            //TODO: Should pass cancellation token here\r
-            var treeHash = localTreeHash ?? Signature.CalculateTreeHashAsync(localPath, (int)serverHash.BlockSize, serverHash.BlockHash, 2);\r
+            var fileName = Path.GetFileName(localPath);\r
+            var progress = new Progress<double>(d =>\r
+                StatusNotification.Notify(new StatusNotification(String.Format("Hashing for Download {0} of {1}", d, fileName))));\r
+            \r
+            var treeHash = localTreeHash ?? Signature.CalculateTreeHashAsync(localPath, (int)serverHash.BlockSize, serverHash.BlockHash, Settings.HashingParallelism,cancellationToken,progress);\r
 \r
             //And compare it with the server's hash\r
             var upHashes = serverHash.GetHashesAsStrings();\r
@@ -176,14 +203,14 @@ namespace Pithos.Core.Agents
             ReportDownloadProgress(Path.GetFileName(localPath), 0,0, upHashes.Length, cloudFile.Bytes);\r
 \r
 \r
-            int i = 0;\r
+            long i = 0;\r
             client.DownloadProgressChanged += (sender, args) =>\r
                                 ReportDownloadProgress(Path.GetFileName(localPath), i, args.ProgressPercentage, upHashes.Length, cloudFile.Bytes);\r
 \r
 \r
             for (i = 0; i < upHashes.Length; i++)\r
             {\r
-                if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken))\r
+                if (await WaitOrAbort(accountInfo, cloudFile, cancellationToken).ConfigureAwait(false))\r
                     return;\r
 \r
                 //For every non-matching hash\r
@@ -199,15 +226,15 @@ namespace Pithos.Core.Agents
                         continue;\r
                     }\r
                     Log.InfoFormat("[BLOCK GET] START {0} of {1} for {2}", i, upHashes.Length, localPath);\r
-                    var start = i * serverHash.BlockSize;\r
+                    long start = i * serverHash.BlockSize;\r
                     //To download the last block just pass a null for the end of the range\r
                     long? end = null;\r
                     if (i < upHashes.Length - 1)\r
                         end = ((i + 1) * serverHash.BlockSize);\r
 \r
-                    //TODO: Pass token here\r
+                    \r
                     //Download the missing block\r
-                    var block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end,cancellationToken);\r
+                    byte[] block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end, cancellationToken).ConfigureAwait(false);\r
 \r
                     //and store it\r
                     blockUpdater.StoreBlock(i, block);\r
@@ -227,6 +254,7 @@ namespace Pithos.Core.Agents
                 StatusNotification.NotifyChangedFile(localPath);\r
 \r
             Log.InfoFormat("[BLOCK GET] COMPLETE {0}", localPath);\r
+            \r
         }\r
 \r
         //Download a small file with a single GET operation\r
@@ -246,7 +274,7 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");\r
             Contract.EndContractBlock();\r
 \r
-            if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken))\r
+            if (await WaitOrAbort(accountInfo, cloudFile, cancellationToken).ConfigureAwait(false))\r
                 return;\r
 \r
             var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);\r
@@ -264,10 +292,8 @@ namespace Pithos.Core.Agents
             if (!Directory.Exists(tempFolder))\r
                 Directory.CreateDirectory(tempFolder);\r
 \r
-            //TODO: Should pass the token here\r
-\r
             //Download the object to the temporary location\r
-            await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath,cancellationToken);\r
+            await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl, tempPath, cancellationToken).ConfigureAwait(false);\r
 \r
             //Create the local folder if it doesn't exist (necessary for shared objects)\r
             var localFolder = Path.GetDirectoryName(localPath);\r
@@ -302,14 +328,14 @@ namespace Pithos.Core.Agents
         }\r
 \r
 \r
-        private void ReportDownloadProgress(string fileName, int block, int blockPercentage,int totalBlocks, long fileSize)\r
+        private void ReportDownloadProgress(string fileName, long block, int blockPercentage,int totalBlocks, long fileSize)\r
         {\r
             StatusNotification.Notify(totalBlocks == 0\r
                                           ? new ProgressNotification(fileName, "Downloading", 1, blockPercentage,1, fileSize)\r
                                           : new ProgressNotification(fileName, "Downloading", block, blockPercentage, totalBlocks, fileSize));\r
         }\r
 \r
-        private bool IsObjectChanged(ObjectInfo cloudFile, string localPath)\r
+        private bool IsObjectChanged(ObjectInfo cloudFile, string localPath,TreeHash localTreeHash)\r
         {\r
             //If the target is a directory, there are no changes to download\r
             if (Directory.Exists(localPath))\r
@@ -322,10 +348,9 @@ namespace Pithos.Core.Agents
             if (localState == null)\r
                 return true;\r
 \r
-            var info = new FileInfo(localPath);\r
-            var shortHash = info.ComputeShortHash();\r
+            var localHash= localTreeHash.TopHash.ToHashString();\r
             //If the file is different from the stored state, we have a change\r
-            if (localState.ShortHash != shortHash)\r
+            if (localState.Checksum != localHash)\r
                 return true;\r
             //If the top hashes differ, we have a change\r
             return (localState.Checksum != cloudFile.X_Object_Hash);\r
@@ -339,7 +364,7 @@ namespace Pithos.Core.Agents
         private async Task<bool> WaitOrAbort(AccountInfo account,ObjectInfo cloudFile, CancellationToken token)\r
         {\r
             token.ThrowIfCancellationRequested();\r
-            await UnpauseEvent.WaitAsync();\r
+            await UnpauseEvent.WaitAsync().ConfigureAwait(false);\r
             var shouldAbort = !Selectives.IsSelected(account,cloudFile);\r
             if (shouldAbort)\r
                 Log.InfoFormat("Aborting [{0}]", cloudFile.Uri);\r