Package updates, added test server
[pithos-ms-client] / trunk / Pithos.Network / Signature.cs
index 389dd79..e3281db 100644 (file)
-#region
-/* -----------------------------------------------------------------------
- * <copyright file="Signature.cs" company="GRNet">
- * 
- * Copyright 2011-2012 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>
- * -----------------------------------------------------------------------
- */
-#endregion
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics.Contracts;
-using System.IO;
-using System.Reflection;
-using System.Runtime.Remoting.Metadata.W3cXsd2001;
-using System.Security.Cryptography;
-using System.Threading.Tasks;
-using System.Linq;
-
-namespace Pithos.Network
-{
-    public static class Signature
-    {
-        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
-
-        public static string CalculateMD5(FileInfo info)
-        {
-            if (info==null)
-                throw new ArgumentNullException("info");
-            if (String.IsNullOrWhiteSpace(info.FullName))
-                throw new ArgumentException("info.FullName is empty","info");
-            Contract.EndContractBlock();
-
-            return CalculateMD5(info.FullName);
-        }
-
-        public static string CalculateMD5(string path)
-        {
-            if (String.IsNullOrWhiteSpace(path))
-                throw new ArgumentNullException("path");
-            Contract.EndContractBlock();
-
-            //DON'T calculate hashes for folders
-            if (Directory.Exists(path))
-                return "";
-
-            string hash;
-            using (var hasher = MD5.Create())
-            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65536, true))
-            {
-                var hashBytes = hasher.ComputeHash(stream);
-                hash = hashBytes.ToHashString();
-            }
-            return hash;
-        }
-
-/*
-        public static string BytesToString(byte[] hashBytes)
-        {
-            var shb=new SoapHexBinary(hashBytes);
-            return shb.ToString();
-            
-        }
-
-
-        public static byte[] StringToBytes(string hash)
-        {
-            var shb=SoapHexBinary.Parse(hash);
-            return shb.Value;
-        }
-*/
-
-        public static byte[] ToBytes(this string hash)
-        {
-            var shb = SoapHexBinary.Parse(hash);
-            return shb.Value;
-        }
-
-        public static string ToHashString(this byte[] hashBytes)
-        {
-            var shb = new SoapHexBinary(hashBytes);
-            return shb.ToString().ToLower();
-        }
-
-        public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, int blockSize, string algorithm)
-        {
-            if (fileInfo == null)
-                throw new ArgumentNullException("fileInfo");
-            if (String.IsNullOrWhiteSpace(fileInfo.FullName))
-                throw new ArgumentException("fileInfo.FullName is empty", "fileInfo");
-            if (blockSize <= 0)
-                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
-            Contract.EndContractBlock();
-
-            if (fileInfo is DirectoryInfo || !fileInfo.Exists)
-                return TreeHash.Empty;
-
-            return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm);
-        }
-
-        /// <summary>
-        /// Calculates a file's tree hash synchronously, using the specified block size
-        /// </summary>
-        /// <param name="filePath">Path to an existing file</param>
-        /// <param name="blockSize">Block size used to calculate leaf hashes</param>
-        /// <param name="algorithm"></param>
-        /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
-        public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm)
-        {
-            if (String.IsNullOrWhiteSpace(filePath))
-                throw new ArgumentNullException("filePath");
-            if (blockSize<=0)
-                throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero ");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
-            Contract.EndContractBlock();
-
-            var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 2);
-            return hash.Result;
-        }
-        
-        public static async Task<TreeHash> CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism)
-        {
-            if (fileInfo == null)
-                throw new ArgumentNullException("fileInfo");
-            if (String.IsNullOrWhiteSpace(fileInfo.FullName))
-                throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
-            if (blockSize <= 0)
-                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
-            Contract.EndContractBlock();
-            
-            return await CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism);
-        }
-
-
-        public static async Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism)
-        {
-            if (String.IsNullOrWhiteSpace(filePath))
-                throw new ArgumentNullException("filePath");
-            if (blockSize <= 0)
-                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
-            Contract.EndContractBlock();
-
-            if (Log.IsDebugEnabled)
-                Log.DebugFormat("Calc Signature [{0}]",filePath);
-
-            //DON'T calculate hashes for folders
-            if (Directory.Exists(filePath))
-                return new TreeHash(algorithm);
-            //The hash of a non-existent file is the empty hash
-            if (!File.Exists(filePath))
-                return new TreeHash(algorithm);
-
-            //Calculate the hash of all blocks using a blockhash iterator
-            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
-            {
-                //Calculate the blocks asyncrhonously
-                var hashes = await BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism);                
-
-                //And then proceed with creating and returning a TreeHash
-                var length = stream.Length;
-                var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
-
-                var treeHash = new TreeHash(algorithm)
-                {
-                    Bytes = length,
-                    BlockSize = blockSize,
-                    Hashes = list,
-                };
-
-                string fileHash;
-                var hasher = HashAlgorithm.Create("MD5");
-                stream.Position = 0;
-                treeHash.MD5= hasher.ComputeHash(stream).ToHashString();
-
-                return treeHash;
-            }
-        }
-
-        
-        public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
-        {
-            if (hashMap == null)
-                throw new ArgumentNullException("hashMap");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
-            Contract.EndContractBlock();            
-
-            var hashCount = hashMap.Count;
-            //The tophash of an empty hashmap is an empty array
-            if (hashCount == 0)
-                return new byte[0];
-            //The tophash of a one-item hashmap is the hash itself
-            if (hashCount == 1)
-                return hashMap[0];
-
-            //Calculate the required number of leaf nodes
-            var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
-            //The size of all nodes is the same and equal to the size of the input hashes
-            var hashSize = hashMap[0].Length;
-
-            //If the hashmap containes fewer nodes than the required leaf count, we need to fill
-            //the rest with empty blocks
-            byte[] empty=null;            
-            if (hashCount < leafs)
-                empty = new byte[hashSize];
-
-            //New hashes will be stored in a dictionary keyed by their step to preserve order
-            var newHashes=new ConcurrentDictionary<int, byte[]>();            
-            
-            Parallel.For(0, leafs/2,
-                (step, state) =>
-                {
-                    using (var hasher = HashAlgorithm.Create(algorithm))
-                    {
-                        var i = step*2;
-                        var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
-                        var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
-
-                        hasher.TransformBlock(block1, 0, block1.Length, null, 0);
-                        hasher.TransformFinalBlock(block2, 0, block2.Length);
-
-                        var finalHash = hasher.Hash;
-                        //Store the final value in its proper place
-                        newHashes[step] = finalHash;
-                    }
-                });
-
-            //Extract the hashes to a list ordered by their step 
-            var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
-            return CalculateTopHash(hashes, algorithm);                   
-        }        
-    
-
-        public static byte[] CalculateHash(byte[] buffer,string algorithm)
-        {
-            if (buffer == null)
-                throw new ArgumentNullException("buffer");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
-            Contract.EndContractBlock();
-
-            using (var hasher = HashAlgorithm.Create(algorithm))
-            {
-                var hash = hasher.ComputeHash(buffer, 0, buffer.Length);
-                return hash;
-            }        
-        }
-    }
-}
-
-
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="Signature.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.Concurrent;\r
+using System.Collections.Generic;\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Reflection;\r
+using System.Runtime.Remoting.Metadata.W3cXsd2001;\r
+using System.Threading;\r
+using System.Threading.Tasks;\r
+using System.Linq;\r
+using Pithos.Interfaces;\r
+using log4net;\r
+using OpenSSL.Core;\r
+using OpenSSL.Crypto;\r
+\r
+namespace Pithos.Network\r
+{\r
+    public static class Signature\r
+    {\r
+        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
+        public const  int BufferSize = 16384;\r
+\r
+//        public const string MD5_EMPTY = "d41d8cd98f00b204e9800998ecf8427e";\r
+        public const string MERKLE_EMPTY = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";\r
+\r
+\r
+        public static byte[] ToBytes(this string hash)\r
+        {\r
+            var shb = SoapHexBinary.Parse(hash);\r
+            return shb.Value;\r
+        }\r
+\r
+        public static string ToHashString(this byte[] hashBytes)\r
+        {\r
+            var shb = new SoapHexBinary(hashBytes);\r
+            return shb.ToString().ToLower();\r
+        }\r
+\r
+        public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, int blockSize, string algorithm,byte parallelism,CancellationToken token,IProgress<HashProgress> progress )\r
+        {\r
+            if (fileInfo == null)\r
+                throw new ArgumentNullException("fileInfo");\r
+            if (String.IsNullOrWhiteSpace(fileInfo.FullName))\r
+                throw new ArgumentException("fileInfo.FullName is empty", "fileInfo");\r
+            if (blockSize <= 0)\r
+                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");\r
+            if (String.IsNullOrWhiteSpace(algorithm))\r
+                throw new ArgumentNullException("algorithm");\r
+            Contract.EndContractBlock();\r
+            fileInfo.Refresh();\r
+            if (fileInfo is DirectoryInfo || !fileInfo.Exists)\r
+                return TreeHash.Empty;\r
+\r
+            return TaskEx.Run(async ()=>await CalculateTreeHashAsync(fileInfo, blockSize, algorithm, parallelism, token, progress)).Result;\r
+        }\r
+\r
+        /// <summary>\r
+        /// Calculates a file's tree hash synchronously, using the specified block size\r
+        /// </summary>\r
+        /// <param name="filePath">Path to an existing file</param>\r
+        /// <param name="blockSize">Block size used to calculate leaf hashes</param>\r
+        /// <param name="algorithm"></param>\r
+        /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>\r
+        public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm,CancellationToken token,IProgress<HashProgress> progress )\r
+        {\r
+            if (String.IsNullOrWhiteSpace(filePath))\r
+                throw new ArgumentNullException("filePath");\r
+            if (blockSize<=0)\r
+                throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero ");\r
+            if (String.IsNullOrWhiteSpace(algorithm))\r
+                throw new ArgumentNullException("algorithm");\r
+            Contract.EndContractBlock();\r
+\r
+            var info = FileInfoExtensions.FromPath(filePath);\r
+            var hash=TaskEx.Run(async ()=>await CalculateTreeHashAsync(info, blockSize, algorithm, 1,token,progress).ConfigureAwait(false)).Result;\r
+            return hash;\r
+        }\r
+        \r
+\r
+        public static Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize, string algorithm, byte parallelism,CancellationToken token,IProgress<HashProgress> progress )\r
+        {\r
+            if (filePath== null)\r
+                throw new ArgumentNullException("filePath");\r
+            if (blockSize <= 0)\r
+                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");\r
+            if (String.IsNullOrWhiteSpace(algorithm))\r
+                throw new ArgumentNullException("algorithm");\r
+            Contract.EndContractBlock();\r
+\r
+            var info = FileInfoExtensions.FromPath(filePath);\r
+            return CalculateTreeHashAsync(info, blockSize, algorithm, parallelism,token,progress);\r
+        }\r
+\r
+\r
+\r
+        public static async Task<TreeHash> CalculateTreeHashAsync(FileSystemInfo info, int blockSize,string algorithm, byte parallelism,CancellationToken token,IProgress<HashProgress> progress )\r
+        {\r
+            if (info==null)\r
+                throw new ArgumentNullException("info");\r
+            if (blockSize <= 0)\r
+                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");\r
+            if (String.IsNullOrWhiteSpace(algorithm))\r
+                throw new ArgumentNullException("algorithm");\r
+            Contract.EndContractBlock();\r
+\r
+            var filePath = info.FullName;\r
+\r
+            if (Log.IsDebugEnabled)\r
+                Log.DebugFormat("Calc Signature [{0}]",filePath);\r
+\r
+            if (filePath.Split('/').Contains(".pithos.cache"))\r
+                throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]",filePath));\r
+\r
+            //DON'T calculate hashes for folders\r
+            if (Directory.Exists(filePath))\r
+                return new TreeHash(algorithm);\r
+            //The hash of a non-existent file is the empty hash\r
+            if (!File.Exists(filePath))\r
+                return new TreeHash(algorithm);            \r
+\r
+            if (info is FileInfo && (info as FileInfo).Length==0)\r
+                return new TreeHash(algorithm);\r
+\r
+            //Calculate the hash of all blocks using a blockhash iterator\r
+            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, BufferSize, true))\r
+            {\r
+                //var md5 = new MD5BlockCalculator();\r
+                //Action<long, byte[], int> postAction = md5.PostBlock;\r
+                //Calculate the blocks asyncrhonously\r
+                var hashes = await BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism,token, progress).ConfigureAwait(false);\r
+                //var hashes = BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism, postAction, token, progress).Result; \r
+\r
+                //And then proceed with creating and returning a TreeHash\r
+                var length = stream.Length;\r
+                var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();\r
+\r
+                var treeHash = new TreeHash(algorithm)\r
+                {\r
+                    Bytes = length,\r
+                    BlockSize = blockSize,\r
+                    Hashes = list,\r
+                };\r
+\r
+                //string fileHash;\r
+\r
+                //var md5Hash=md5.GetHash().Result;\r
+                //treeHash.MD5= md5Hash;\r
+\r
+                return treeHash;\r
+            }\r
+        }\r
+\r
+        \r
+        public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)\r
+        {\r
+            if (hashMap == null)\r
+                throw new ArgumentNullException("hashMap");\r
+            if (String.IsNullOrWhiteSpace(algorithm))\r
+                throw new ArgumentNullException("algorithm");\r
+            Contract.EndContractBlock();            \r
+\r
+            var hashCount = hashMap.Count;\r
+            //TODO: Replace this calculation with a constant\r
+            //The tophash of an empty hashmap is the hash of an empty array\r
+            if (hashCount == 0)\r
+            {\r
+                using (var hasher = new MessageDigestContext(MessageDigest.CreateByName(algorithm)))\r
+                {\r
+                    hasher.Init();\r
+                    var emptyHash=hasher.Digest(new byte[0]);\r
+                    return emptyHash;\r
+                }                \r
+            }\r
+            //The tophash of a one-item hashmap is the hash itself\r
+            if (hashCount == 1)\r
+                return hashMap[0];\r
+\r
+            //Calculate the required number of leaf nodes\r
+            var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));\r
+            //The size of all nodes is the same and equal to the size of the input hashes\r
+            var hashSize = hashMap[0].Length;\r
+\r
+            //If the hashmap containes fewer nodes than the required leaf count, we need to fill\r
+            //the rest with empty blocks\r
+            byte[] empty=null;            \r
+            if (hashCount < leafs)\r
+                empty = new byte[hashSize];\r
+\r
+            //New hashes will be stored in a dictionary keyed by their step to preserve order\r
+            var newHashes=new ConcurrentDictionary<int, byte[]>();            \r
+            \r
+            Parallel.For(0, leafs/2,\r
+                (step, state) =>\r
+                {\r
+                    using (var hasher = new MessageDigestContext(MessageDigest.CreateByName(algorithm)))\r
+                    {\r
+                        hasher.Init();\r
+                        var i = step*2;\r
+                        var block1 = i <= hashCount - 1 ? hashMap[i] : empty;\r
+                        var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;\r
+\r
+                        hasher.Update(block1);\r
+                        hasher.Update(block2);\r
+\r
+                        var finalHash = hasher.DigestFinal();\r
+                        //Store the final value in its proper place\r
+                        newHashes[step] = finalHash;\r
+                    }\r
+                });\r
+\r
+            //Extract the hashes to a list ordered by their step \r
+            var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();\r
+            return CalculateTopHash(hashes, algorithm);                   \r
+        }        \r
+    \r
+\r
+        public static byte[] CalculateHash(byte[] buffer,string algorithm)\r
+        {\r
+            if (buffer == null)\r
+                throw new ArgumentNullException("buffer");\r
+            if (String.IsNullOrWhiteSpace(algorithm))\r
+                throw new ArgumentNullException("algorithm");\r
+            Contract.EndContractBlock();\r
+\r
+            using (var hasher = new MessageDigestContext(MessageDigest.CreateByName(algorithm)))\r
+            {\r
+                hasher.Init();\r
+                var hash = hasher.Digest(buffer);\r
+                return hash;\r
+            }        \r
+        }\r
+    }\r
+\r
+    public class HashProgress\r
+    {\r
+        public HashProgress(long current,long total)\r
+        {\r
+            Current = current;\r
+            Total = total;\r
+\r
+        }\r
+        public float Percentage { get\r
+        {\r
+            if (Current > Total)\r
+                return 1;\r
+            return Current/(float)Total;\r
+        }}\r
+        public long Current { get; set; }\r
+        public long Total { get; set; }\r
+\r
+    }\r
+\r
+}\r
+\r
+\r
   
\ No newline at end of file