Revision a27aa447 trunk/Pithos.Network/Signature.cs

b/trunk/Pithos.Network/Signature.cs
13 13
{
14 14
    public static class Signature
15 15
    {
16
        public static string CalculateMD5(FileInfo info)
17
        {
18
            if (info==null)
19
                throw new ArgumentNullException("info");
20
            Contract.EndContractBlock();
21

  
22
            return CalculateMD5(info.FullName);
23
        }
24

  
16 25
        public static string CalculateMD5(string path)
17 26
        {
18 27
            if (String.IsNullOrWhiteSpace(path))
......
61 70
            return shb.ToString().ToLower();
62 71
        }
63 72

  
73
        public static TreeHash CalculateTreeHash(FileInfo fileInfo, int blockSize, string algorithm)
74
        {
75
            if (fileInfo==null)
76
                throw new ArgumentNullException("fileInfo");
77
            if (blockSize <= 0)
78
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
79
            if (String.IsNullOrWhiteSpace(algorithm))
80
                throw new ArgumentNullException("algorithm");
81
            Contract.EndContractBlock();
82

  
83
            return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm);
84
        }
64 85

  
65 86
        /// <summary>
66 87
        /// Calculates a file's tree hash synchronously, using the specified block size
......
88 109
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, false))
89 110
            using (var hasher = HashAlgorithm.Create(algorithm))
90 111
            {
112
                var buffer = new byte[blockSize];
91 113
                int read;
92
                var buffer = new byte[blockSize];            
93
                while ((read = stream.Read(buffer, 0, blockSize)) > 0)
114
                while ((read=stream.Read(buffer, 0, blockSize)) > 0)
94 115
                {
95
                    var hash = hasher.ComputeHash(buffer, 0, read);
96
                    list.Add(hash);
116
                    //This code was added for compatibility with the way Pithos calculates the last hash
117
                    //We calculate the hash only up to the last non-null byte
118
                    //TODO: Remove if the server starts using the full block instead of the trimmed block
119
                    var lastByteIndex=Array.FindLastIndex(buffer,read-1, aByte => aByte != 0);
120
                    
121
                    var hash = hasher.ComputeHash(buffer, 0, lastByteIndex+1);
122
                    list.Add(hash);                    
97 123
                }
98 124
                return new TreeHash(algorithm) { Hashes = list,                    
99 125
                    BlockSize = blockSize, 
100 126
                    Bytes = stream.Length};
101 127
            }            
102 128
        }
129
        
130
        public static Task<TreeHash> CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm)
131
        {
132
            if (fileInfo == null)
133
                throw new ArgumentNullException("fileInfo");
134
            if (blockSize <= 0)
135
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
136
            if (String.IsNullOrWhiteSpace(algorithm))
137
                throw new ArgumentNullException("algorithm");
138
            Contract.EndContractBlock();
139

  
140
            return CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm);
141
        }
103 142

  
104 143

  
105 144
        public static Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize,string algorithm)
......
116 155
            if (Directory.Exists(filePath))
117 156
                return Task.Factory.StartNew(()=>new TreeHash(algorithm));
118 157

  
158
            //Calculate the hash of all blocks using a blockhash iterator
159
            var treeHash =Iterate<TreeHash>(BlockHashIterator(filePath, blockSize, algorithm));
160
            
161
            return treeHash;
162
        }
163

  
164
        
165
        private static IEnumerable<Task> BlockHashIterator(string filePath, int blockSize, string algorithm)
166
        {
167
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
168
            {
169
                //Calculate the blocks asyncrhonously
170
                var hashes= CalculateBlockHashesAsync(stream, blockSize, algorithm);
171
                yield return hashes;
172
                
173
                //And then proceed with creating and returning a TreeHash
174
                var length = stream.Length;                
175
                var list = hashes.Result.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
176

  
177
                var treeHash = new TreeHash(algorithm)
178
                {
179
                    Bytes = length, 
180
                    BlockSize = blockSize, 
181
                    Hashes = list
182
                };
183

  
184
                yield return Task.Factory.FromResult(treeHash);
185
            }
119 186

  
120
            var hashes = new ConcurrentDictionary<int, byte[]>();
121
            var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true);
187
        }
122 188

  
123
            return CalculateBlockHashesAsync(stream, blockSize, algorithm,hashes)
124
                .ContinueWith(t => {
125
                                        var length = stream.Length;
126
                                       stream.Close();
127
                                       var list= t.Result.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();                                       
128
                                       return new TreeHash(algorithm) { Hashes = list,                                           
129
                                           BlockSize = blockSize, 
130
                                           Bytes = length };
131
                });
189
        //The Task Iterator style allows the execution of a sequence of patterns using
190
        //iterator syntax.
191
        //This particular variation returns the result of the last task, if there is one
192
        public static Task<TResult> Iterate<TResult>(IEnumerable<Task> asyncIterator)
193
        {
194
            if (asyncIterator == null) 
195
                throw new ArgumentNullException("asyncIterator");
196
            var enumerator = asyncIterator.GetEnumerator();
197
            if (enumerator == null)
198
                throw new InvalidOperationException("Invalid enumerable - GetEnumerator returned null");
199
            
200
            var tcs = new TaskCompletionSource<TResult>();
201
            tcs.Task.ContinueWith(_ => enumerator.Dispose(), TaskContinuationOptions.ExecuteSynchronously);
202
            
203
            Action<Task> recursiveBody = null;
204
            recursiveBody = delegate
205
            {
206
                try
207
                {
208
                    if (enumerator.MoveNext())
209
                        enumerator.Current.ContinueWith(recursiveBody,
210
                                                        TaskContinuationOptions.ExecuteSynchronously);
211
                    else
212
                    {
213
                        var lastTask = enumerator.Current as Task<TResult>;
214
                        var result = (lastTask !=null ) 
215
                            ? lastTask.Result
216
                            : default(TResult);
217

  
218
                        tcs.TrySetResult(result);
219
                    }
220
                }
221
                catch (Exception exc)
222
                {
223
                    tcs.TrySetException(exc);
224
                }
225
            };
226
            recursiveBody(null);
227
            return tcs.Task;
132 228
        }
133 229

  
134
      /*  public static byte[] CalculateTopHash(IEnumerable<byte[]> hashMap, string algorithm)
230

  
231
        /*  public static byte[] CalculateTopHash(IEnumerable<byte[]> hashMap, string algorithm)
135 232
        {
136 233
            if (hashMap == null)
137 234
                throw new ArgumentNullException("hashMap");
......
251 348
            }
252 349
        }*/
253 350

  
254
        private static Task<ConcurrentDictionary<int, byte[]>> CalculateBlockHashesAsync(FileStream stream, int blockSize, string algorithm, ConcurrentDictionary<int, byte[]> hashes, int index = 0)
351
        private static Task<ConcurrentDictionary<int, byte[]>> CalculateBlockHashesAsync(FileStream stream, int blockSize, string algorithm, ConcurrentDictionary<int, byte[]> hashes=null, int index = 0)
255 352
        {
256 353
            if (stream==null)
257 354
                throw new ArgumentNullException("stream");
258
            if (hashes==null)
259
                throw new ArgumentNullException("hashes");
260 355
            if (String.IsNullOrWhiteSpace(algorithm))
261 356
                throw new ArgumentNullException("algorithm");
262 357
            if (blockSize <= 0)
......
266 361
            Contract.EndContractBlock();
267 362

  
268 363

  
364
            if (hashes==null)
365
                hashes= new ConcurrentDictionary<int, byte[]>();
366

  
269 367
            var buffer = new byte[blockSize];
270 368
            return stream.ReadAsync(buffer, 0, blockSize).ContinueWith(t =>
271 369
            {
......
274 372
                var nextTask = read == blockSize
275 373
                                    ? CalculateBlockHashesAsync(stream, blockSize, algorithm, hashes, index + 1) 
276 374
                                    : Task.Factory.StartNew(() => hashes);
375
                if (read>0)
376
                    using (var hasher = HashAlgorithm.Create(algorithm))
377
                    {
378
                        //This code was added for compatibility with the way Pithos calculates the last hash
379
                        //We calculate the hash only up to the last non-null byte
380
                        //TODO: Remove if the server starts using the full block instead of the trimmed block
381
                        var lastByteIndex = Array.FindLastIndex(buffer, read - 1, aByte => aByte != 0);
277 382

  
278
                using (var hasher = HashAlgorithm.Create(algorithm))
279
                {
280
                    var hash = hasher.ComputeHash(buffer, 0, read);
281
                    hashes[index]=hash;
282
                }
383
                        var hash = hasher.ComputeHash(buffer, 0, lastByteIndex+1);
384
                        hashes[index]=hash;
385
                    }
283 386
                return nextTask;
284 387
            }).Unwrap();
285 388
        }

Also available in: Unified diff