Changed ETag calculation to SHA256
[pithos-ms-client] / trunk / Pithos.Core / Agents / BlockExtensions.cs
1 #region\r
2 /* -----------------------------------------------------------------------\r
3  * <copyright file="BlockExtensions.cs" company="GRNet">\r
4  * \r
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
6  *\r
7  * Redistribution and use in source and binary forms, with or\r
8  * without modification, are permitted provided that the following\r
9  * conditions are met:\r
10  *\r
11  *   1. Redistributions of source code must retain the above\r
12  *      copyright notice, this list of conditions and the following\r
13  *      disclaimer.\r
14  *\r
15  *   2. Redistributions in binary form must reproduce the above\r
16  *      copyright notice, this list of conditions and the following\r
17  *      disclaimer in the documentation and/or other materials\r
18  *      provided with the distribution.\r
19  *\r
20  *\r
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
32  * POSSIBILITY OF SUCH DAMAGE.\r
33  *\r
34  * The views and conclusions contained in the software and\r
35  * documentation are those of the authors and should not be\r
36  * interpreted as representing official policies, either expressed\r
37  * or implied, of GRNET S.A.\r
38  * </copyright>\r
39  * -----------------------------------------------------------------------\r
40  */\r
41 #endregion\r
42 using System;\r
43 using System.Collections.Generic;\r
44 using System.Diagnostics;\r
45 using System.Diagnostics.Contracts;\r
46 using System.Linq;\r
47 using System.Reflection;\r
48 using System.Security.Cryptography;\r
49 using System.Text;\r
50 using System.IO;\r
51 using System.Text.RegularExpressions;\r
52 using System.Threading;\r
53 using System.Threading.Tasks;\r
54 using Pithos.Network;\r
55 using log4net;\r
56 \r
57 namespace Pithos.Core.Agents\r
58 {\r
59     static class BlockExtensions\r
60     {\r
61 \r
62         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
63 \r
64         public static int Read(this FileInfo fileInfo,byte[] buffer,long offset,int count)\r
65         {\r
66             if (offset < 0)\r
67                 throw new ArgumentOutOfRangeException("offset", offset, "The file offset can't be negative");\r
68             Contract.EndContractBlock();\r
69             //Open the stream only long enough to read a block\r
70             using (var stream = fileInfo.OpenRead())\r
71             {\r
72                 stream.Seek(offset, SeekOrigin.Begin);\r
73                 return  stream.Read(buffer, 0, count);                \r
74             }\r
75         }\r
76 \r
77     \r
78        public static string CalculateHash(this FileSystemInfo info,int blockSize,string algorithm,CancellationToken token,IProgress<double> progress )\r
79         {\r
80             if (info==null)\r
81                 throw new ArgumentNullException("info");\r
82             if (String.IsNullOrWhiteSpace(info.FullName))\r
83                 throw new ArgumentException("info");\r
84             if (blockSize<=0)\r
85                 throw new ArgumentOutOfRangeException("blockSize",blockSize,"blockSize must be greater than 0");\r
86             if (String.IsNullOrWhiteSpace(algorithm))\r
87                 throw new ArgumentNullException("algorithm");\r
88             Contract.EndContractBlock();\r
89            info.Refresh();\r
90            if (info.FullName.Split('/').Contains(".pithos.cache"))\r
91                throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]", info.FullName));\r
92 \r
93            //The hash for directories is an empty string\r
94            if (info is DirectoryInfo)\r
95                 return String.Empty;\r
96            //The hash for non-existent files is an empty string\r
97            if (!info.Exists)\r
98                return String.Empty;\r
99 \r
100            return Signature.CalculateTreeHash(info.FullName, blockSize, algorithm,token,progress).TopHash.ToHashString();\r
101 \r
102         }\r
103 \r
104        /// <summary>\r
105        ///Calculates a simple hash for an entire file\r
106        /// </summary>\r
107        /// <param name="info">The file to hash</param>\r
108        /// <param name="hasher">The hash algorithm to use</param>\r
109        /// <returns>A hash value for the entire file. An empty string if the file does not exist.</returns>\r
110        public static string ComputeShortHash(this FileInfo info, HashAlgorithm hasher,IStatusNotification notification)\r
111        {\r
112            if(info == null)\r
113                throw new ArgumentNullException("info");\r
114            if(hasher== null)\r
115                throw new ArgumentNullException("hasher");\r
116            Contract.EndContractBlock();\r
117            info.Refresh();\r
118 \r
119            if (!info.Exists)\r
120                return String.Empty;\r
121            if (info.FullName.Split('/').Contains(".pithos.cache"))\r
122                throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]", info.FullName));\r
123 \r
124            if (Log.IsDebugEnabled)\r
125                 Log.DebugFormat("Short Hashing [{0}] ",info.FullName);\r
126 \r
127            if (info.Length==0)\r
128                return Signature.MD5_EMPTY;\r
129 \r
130            var progress = new StatusNotification("");\r
131 \r
132            using (var stream = new FileStream(info.FullName,FileMode.Open, FileAccess.Read, FileShare.Read,Signature.BufferSize))\r
133            {\r
134                var buffer = new byte[65536];\r
135                int counter=0;\r
136                int bytesRead;\r
137                do\r
138                {\r
139                    bytesRead = stream.Read(buffer, 0, 32768);\r
140                    if (bytesRead > 0)\r
141                    {\r
142                        hasher.TransformBlock(buffer, 0, bytesRead, null, 0);\r
143                    }\r
144                    counter++;\r
145                    if (counter % 100 == 0)\r
146                    {\r
147                        progress.Title = String.Format("Hashing {0:p} of {1}", stream.Position*1.0/stream.Length,\r
148                                                       info.Name);\r
149                        notification.Notify(progress);\r
150                    }\r
151                } while (bytesRead > 0);\r
152                hasher.TransformFinalBlock(buffer, 0, 0);\r
153                var hash = hasher.Hash;\r
154 \r
155                progress.Title = String.Format("Hashed {0} ", info.Name);\r
156                notification.Notify(progress);\r
157 \r
158                var hashString = hash.ToHashString();\r
159 \r
160                return hashString;\r
161            }\r
162        }\r
163 \r
164         public static async Task<string> ComputeShortHash(this FileInfo info, MD5BlockCalculator calculator,IStatusNotification notification)\r
165        {\r
166            if(info == null)\r
167                throw new ArgumentNullException("info");\r
168            if(calculator== null)\r
169                throw new ArgumentNullException("calculator");\r
170            Contract.EndContractBlock();\r
171            info.Refresh();\r
172            if (!info.Exists)\r
173                return String.Empty;\r
174 \r
175            if (info.FullName.Split('/').Contains(".pithos.cache"))\r
176                throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]", info.FullName));\r
177 \r
178            if (Log.IsDebugEnabled)\r
179                 Log.DebugFormat("Short Hashing [{0}] ",info.FullName);\r
180 \r
181            if (info.Length == 0)\r
182                return Signature.MD5_EMPTY;\r
183 \r
184            var progress = new StatusNotification("");\r
185 \r
186           \r
187 \r
188            using (var stream = new FileStream(info.FullName,FileMode.Open, FileAccess.Read, FileShare.Read,Signature.BufferSize))\r
189            {\r
190                int counter=0;\r
191                int bytesRead;\r
192                do\r
193                {\r
194                    var buffer = new byte[65536];\r
195                    bytesRead = stream.Read(buffer, 0, 32768);\r
196                    if (bytesRead > 0)\r
197                    {\r
198                        calculator.PostBlock(counter,buffer,bytesRead);\r
199                    }\r
200                    counter++;\r
201                    if (counter % 100 == 0)\r
202                    {\r
203                        progress.Title = String.Format("Hashing {0:p} of {1}", stream.Position*1.0/stream.Length,\r
204                                                       info.Name);\r
205                        notification.Notify(progress);\r
206                    }\r
207                } while (bytesRead > 0);\r
208 \r
209                var hashString = await calculator.GetHash();\r
210                \r
211 \r
212                progress.Title = String.Format("Hashed {0} ", info.Name);\r
213                notification.Notify(progress);\r
214 \r
215 \r
216                return hashString;\r
217            }\r
218        }\r
219 \r
220         public static string ComputeShortHash(this FileInfo info,IStatusNotification notification)\r
221        {\r
222            if(info == null)\r
223                throw new ArgumentNullException("info");\r
224             Contract.EndContractBlock();\r
225             if (info.FullName.Split('/').Contains(".pithos.cache"))\r
226                 throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]", info.FullName));\r
227 \r
228            using (var hasher=HashAlgorithm.Create("md5"))\r
229            {               \r
230                return ComputeShortHash(info,hasher,notification);\r
231            }\r
232        }\r
233 \r
234     }\r
235 }\r