Changed ETag calculation to SHA256
[pithos-ms-client] / trunk / Pithos.Core / Agents / BlockExtensions.cs
index 1b6b103..7cb826c 100644 (file)
-// -----------------------------------------------------------------------
-// <copyright file="FileInfoExtensions.cs" company="GRNET">
-// Copyright 2011 GRNET S.A. All rights reserved.
-// 
-// Redistribution and use in source and binary forms, with or
-// without modification, are permitted provided that the following
-// conditions are met:
-// 
-//   1. Redistributions of source code must retain the above
-//      copyright notice, this list of conditions and the following
-//      disclaimer.
-// 
-//   2. Redistributions in binary form must reproduce the above
-//      copyright notice, this list of conditions and the following
-//      disclaimer in the documentation and/or other materials
-//      provided with the distribution.
-// 
-// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
-// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
-// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
-// 
-// The views and conclusions contained in the software and
-// documentation are those of the authors and should not be
-// interpreted as representing official policies, either expressed
-// or implied, of GRNET S.A.
-// </copyright>
-// -----------------------------------------------------------------------
-
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Contracts;
-using System.Linq;
-using System.Text;
-using System.IO;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using Pithos.Network;
-
-namespace Pithos.Core.Agents
-{
-    static class BlockExtensions
-    {
-        
-
-        public static int Read(this FileInfo fileInfo,byte[] buffer,int offset,int count)
-        {            
-            //Open the stream only long enough to read a block
-            using (var stream = fileInfo.OpenRead())
-            {
-                stream.Seek(offset, SeekOrigin.Begin);
-                return  stream.Read(buffer, 0, count);                
-            }
-        }
-
-       public static string CalculateHash(this FileSystemInfo info,int blockSize,string algorithm)
-        {
-            if (info==null)
-                throw new ArgumentNullException("info");
-            if (String.IsNullOrWhiteSpace(info.FullName))
-                throw new ArgumentException("info");
-            if (blockSize<=0)
-                throw new ArgumentOutOfRangeException("blockSize",blockSize,"blockSize must be greater than 0");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
-            Contract.EndContractBlock();
-
-           //The hash for directories is an empty string
-           if (info is DirectoryInfo)
-                return String.Empty;
-           
-           var fileInfo = (FileInfo)info;
-           if (fileInfo.Length <= blockSize)
-                return Signature.CalculateMD5(info.FullName);
-            else
-                return Signature.CalculateTreeHash(info.FullName, blockSize, algorithm).TopHash.ToHashString();
-
-        }
-    }
-}
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="BlockExtensions.cs" company="GRNet">\r
+ * \r
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer in the documentation and/or other materials\r
+ *      provided with the distribution.\r
+ *\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * The views and conclusions contained in the software and\r
+ * documentation are those of the authors and should not be\r
+ * interpreted as representing official policies, either expressed\r
+ * or implied, of GRNET S.A.\r
+ * </copyright>\r
+ * -----------------------------------------------------------------------\r
+ */\r
+#endregion\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Diagnostics;\r
+using System.Diagnostics.Contracts;\r
+using System.Linq;\r
+using System.Reflection;\r
+using System.Security.Cryptography;\r
+using System.Text;\r
+using System.IO;\r
+using System.Text.RegularExpressions;\r
+using System.Threading;\r
+using System.Threading.Tasks;\r
+using Pithos.Network;\r
+using log4net;\r
+\r
+namespace Pithos.Core.Agents\r
+{\r
+    static class BlockExtensions\r
+    {\r
+\r
+        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
+\r
+        public static int Read(this FileInfo fileInfo,byte[] buffer,long offset,int count)\r
+        {\r
+            if (offset < 0)\r
+                throw new ArgumentOutOfRangeException("offset", offset, "The file offset can't be negative");\r
+            Contract.EndContractBlock();\r
+            //Open the stream only long enough to read a block\r
+            using (var stream = fileInfo.OpenRead())\r
+            {\r
+                stream.Seek(offset, SeekOrigin.Begin);\r
+                return  stream.Read(buffer, 0, count);                \r
+            }\r
+        }\r
+\r
+    \r
+       public static string CalculateHash(this FileSystemInfo info,int blockSize,string algorithm,CancellationToken token,IProgress<double> progress )\r
+        {\r
+            if (info==null)\r
+                throw new ArgumentNullException("info");\r
+            if (String.IsNullOrWhiteSpace(info.FullName))\r
+                throw new ArgumentException("info");\r
+            if (blockSize<=0)\r
+                throw new ArgumentOutOfRangeException("blockSize",blockSize,"blockSize must be greater than 0");\r
+            if (String.IsNullOrWhiteSpace(algorithm))\r
+                throw new ArgumentNullException("algorithm");\r
+            Contract.EndContractBlock();\r
+           info.Refresh();\r
+           if (info.FullName.Split('/').Contains(".pithos.cache"))\r
+               throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]", info.FullName));\r
+\r
+           //The hash for directories is an empty string\r
+           if (info is DirectoryInfo)\r
+                return String.Empty;\r
+           //The hash for non-existent files is an empty string\r
+           if (!info.Exists)\r
+               return String.Empty;\r
+\r
+           return Signature.CalculateTreeHash(info.FullName, blockSize, algorithm,token,progress).TopHash.ToHashString();\r
+\r
+        }\r
+\r
+       /// <summary>\r
+       ///Calculates a simple hash for an entire file\r
+       /// </summary>\r
+       /// <param name="info">The file to hash</param>\r
+       /// <param name="hasher">The hash algorithm to use</param>\r
+       /// <returns>A hash value for the entire file. An empty string if the file does not exist.</returns>\r
+       public static string ComputeShortHash(this FileInfo info, HashAlgorithm hasher,IStatusNotification notification)\r
+       {\r
+           if(info == null)\r
+               throw new ArgumentNullException("info");\r
+           if(hasher== null)\r
+               throw new ArgumentNullException("hasher");\r
+           Contract.EndContractBlock();\r
+           info.Refresh();\r
+\r
+           if (!info.Exists)\r
+               return String.Empty;\r
+           if (info.FullName.Split('/').Contains(".pithos.cache"))\r
+               throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]", info.FullName));\r
+\r
+           if (Log.IsDebugEnabled)\r
+                Log.DebugFormat("Short Hashing [{0}] ",info.FullName);\r
+\r
+           if (info.Length==0)\r
+               return Signature.MD5_EMPTY;\r
+\r
+           var progress = new StatusNotification("");\r
+\r
+           using (var stream = new FileStream(info.FullName,FileMode.Open, FileAccess.Read, FileShare.Read,Signature.BufferSize))\r
+           {\r
+               var buffer = new byte[65536];\r
+               int counter=0;\r
+               int bytesRead;\r
+               do\r
+               {\r
+                   bytesRead = stream.Read(buffer, 0, 32768);\r
+                   if (bytesRead > 0)\r
+                   {\r
+                       hasher.TransformBlock(buffer, 0, bytesRead, null, 0);\r
+                   }\r
+                   counter++;\r
+                   if (counter % 100 == 0)\r
+                   {\r
+                       progress.Title = String.Format("Hashing {0:p} of {1}", stream.Position*1.0/stream.Length,\r
+                                                      info.Name);\r
+                       notification.Notify(progress);\r
+                   }\r
+               } while (bytesRead > 0);\r
+               hasher.TransformFinalBlock(buffer, 0, 0);\r
+               var hash = hasher.Hash;\r
+\r
+               progress.Title = String.Format("Hashed {0} ", info.Name);\r
+               notification.Notify(progress);\r
+\r
+               var hashString = hash.ToHashString();\r
+\r
+               return hashString;\r
+           }\r
+       }\r
+\r
+        public static async Task<string> ComputeShortHash(this FileInfo info, MD5BlockCalculator calculator,IStatusNotification notification)\r
+       {\r
+           if(info == null)\r
+               throw new ArgumentNullException("info");\r
+           if(calculator== null)\r
+               throw new ArgumentNullException("calculator");\r
+           Contract.EndContractBlock();\r
+           info.Refresh();\r
+           if (!info.Exists)\r
+               return String.Empty;\r
+\r
+           if (info.FullName.Split('/').Contains(".pithos.cache"))\r
+               throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]", info.FullName));\r
+\r
+           if (Log.IsDebugEnabled)\r
+                Log.DebugFormat("Short Hashing [{0}] ",info.FullName);\r
+\r
+           if (info.Length == 0)\r
+               return Signature.MD5_EMPTY;\r
+\r
+           var progress = new StatusNotification("");\r
+\r
+          \r
+\r
+           using (var stream = new FileStream(info.FullName,FileMode.Open, FileAccess.Read, FileShare.Read,Signature.BufferSize))\r
+           {\r
+               int counter=0;\r
+               int bytesRead;\r
+               do\r
+               {\r
+                   var buffer = new byte[65536];\r
+                   bytesRead = stream.Read(buffer, 0, 32768);\r
+                   if (bytesRead > 0)\r
+                   {\r
+                       calculator.PostBlock(counter,buffer,bytesRead);\r
+                   }\r
+                   counter++;\r
+                   if (counter % 100 == 0)\r
+                   {\r
+                       progress.Title = String.Format("Hashing {0:p} of {1}", stream.Position*1.0/stream.Length,\r
+                                                      info.Name);\r
+                       notification.Notify(progress);\r
+                   }\r
+               } while (bytesRead > 0);\r
+\r
+               var hashString = await calculator.GetHash();\r
+               \r
+\r
+               progress.Title = String.Format("Hashed {0} ", info.Name);\r
+               notification.Notify(progress);\r
+\r
+\r
+               return hashString;\r
+           }\r
+       }\r
+\r
+        public static string ComputeShortHash(this FileInfo info,IStatusNotification notification)\r
+       {\r
+           if(info == null)\r
+               throw new ArgumentNullException("info");\r
+            Contract.EndContractBlock();\r
+            if (info.FullName.Split('/').Contains(".pithos.cache"))\r
+                throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]", info.FullName));\r
+\r
+           using (var hasher=HashAlgorithm.Create("md5"))\r
+           {               \r
+               return ComputeShortHash(info,hasher,notification);\r
+           }\r
+       }\r
+\r
+    }\r
+}\r