Added log4net for client profile
[pithos-ms-client] / trunk / Pithos.Network / TreeHash.cs
1 using System;
2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Diagnostics.Contracts;
5 using System.IO;
6 using System.Text;
7 using System.Threading.Tasks;
8 using Newtonsoft.Json;
9 using System.Linq;
10 using Newtonsoft.Json.Linq;
11
12 namespace Pithos.Network
13 {
14     public class TreeHash
15     {
16         private const string DEFAULT_HASH_ALGORITHM = "sha256";
17         private const int DEFAULT_BLOCK_SIZE = 4*1024*1024;
18         public string BlockHash { get; set; }
19         public int BlockSize { get; set; }
20         
21         private long _bytes;
22         public long Bytes
23         {
24             get
25             {
26                 Contract.Ensures(Contract.Result<long>() >= 0);
27                 return _bytes;
28             }
29             set
30             {
31                 if (value<0)
32                     throw new ArgumentOutOfRangeException("Bytes");
33                 Contract.Requires(value >= 0);
34                 Contract.EndContractBlock();
35                 
36                 _bytes = value;
37             }
38         }
39         
40
41
42         public Guid FileId { get; set; }
43
44         private readonly Lazy<byte[]> _topHash;        
45         public byte[] TopHash
46         {
47             get { return _topHash.Value; }
48         }
49
50         private IList<byte[]> _hashes;
51
52         public IList<byte[]> Hashes
53         {
54             get { return _hashes; }
55             set
56             {
57                 _hashes = value;
58                 _topHash.Force();
59             }
60         }
61
62         [ContractInvariantMethod]
63         private void Invariants()
64         {
65             Contract.Invariant(_bytes>=0);
66         }
67         
68
69        public TreeHash(string algorithm)
70         {
71             BlockHash = algorithm;            
72             _topHash = new Lazy<byte[]>(() =>
73             {
74                 //
75                 //Cast the hashes to an IList or create a new list out of the hashes
76                 var hashMap = (_hashes as IList<byte[]>)??_hashes.ToList();
77                 return Signature.CalculateTopHash(hashMap, BlockHash);
78             });
79         }
80
81         //Returns a Json representation of the hashes, as required by Pithos
82         public string ToJson()
83         {
84             var value = new JObject();
85             //We avoid using JObject's dynamic features because they use exceptions to detect new properties.
86             value["block_hash"] = BlockHash;
87             //Create a string array for all the hashes           
88             
89             string[] hashes=null ;
90             if (Hashes!=null)
91                 hashes= GetHashesAsStrings();
92             value["hashes"]= new JArray(hashes);
93             value["block_size"] = BlockSize;
94             value["bytes"] = Bytes;
95             
96             var json = JsonConvert.SerializeObject(value, Formatting.None);
97             return json;
98         }
99
100         private string[] _stringHashes;
101         //Returns the hashes as an array of hash strings. Used for serialization to Json
102         public string[] GetHashesAsStrings()
103         {
104             return _stringHashes 
105                 ?? (_stringHashes = Hashes.Select(hash => hash.ToHashString()).ToArray());
106         }
107
108         ConcurrentDictionary<string, int> _blocks;
109         //Retruns the hashes as a dictionary to the block location. Used to locate blocks
110         public IDictionary<string,int> HashDictionary
111         {
112             get
113             {
114                 Func<ConcurrentDictionary<string, int>> blocksInit = () =>
115                 {
116                     var blocks =
117                         new ConcurrentDictionary<string, int>();
118                     if (Hashes == null)
119                         return blocks;
120
121                     var blockIndex = 0;
122                     foreach (var hash in this.Hashes)
123                     {
124                         blocks[hash.ToHashString()] = blockIndex++;
125                     }
126                     return blocks;
127                 };
128
129                 return _blocks ?? (_blocks = blocksInit());
130             }
131         }
132
133         //Saves the Json representation to a file
134         public Task Save(string filePath)
135         {
136             if (String.IsNullOrWhiteSpace(filePath))
137                 throw new ArgumentNullException("filePath");
138             Contract.EndContractBlock();
139
140             var fileName = FileId.ToString("N");
141             var path = Path.Combine(filePath, fileName);
142             if (!Directory.Exists(filePath))
143                 Directory.CreateDirectory(filePath);
144
145             return Task.Factory
146                 .StartNew<string>(ToJson)
147                 .ContinueWith(jsonTask=> 
148                 FileAsync.WriteAllText(path, jsonTask.Result)).Unwrap();
149         }
150
151         public static Task<TreeHash> LoadTreeHash(string dataPath,Guid fileId)
152         {
153             var fileName = fileId.ToString("N");
154             var path = Path.Combine(dataPath, fileName);
155             return FileAsync.ReadAllText(path).ContinueWith(loadTask =>
156             {
157                 var json = loadTask.Result;
158                 var treeHash = Parse(json);
159                 treeHash.FileId = fileId;
160                 return treeHash;
161             });
162         }
163
164         public static TreeHash Empty = new TreeHash(DEFAULT_HASH_ALGORITHM)
165         {
166             BlockSize = DEFAULT_BLOCK_SIZE,
167             Bytes = 0
168         };
169
170         //Parse a json string and return a TreeHash
171         //Returns an empty TreeHash if the string is null or empty
172         public static TreeHash Parse(string json)
173         {
174             if (String.IsNullOrWhiteSpace(json))
175                 return Empty;            
176
177             var value = JsonConvert.DeserializeObject<JObject>(json);
178             if (value==null)
179                 throw new ArgumentException("The json parameter doesn't contain any json data","json");
180             Contract.Assume(value!=null);
181
182             var blockHash = (string) value["block_hash"];
183             var size = value.Value<int>("block_size");
184             var bytes = value.Value<long>("bytes");
185             var hashes = value.Value<JArray>("hashes");
186             var hashValues = from JToken token in hashes
187                              select token.Value<string>().ToBytes();
188
189             var treeHash = new TreeHash(blockHash)
190                                {
191                                    BlockSize = size,
192                                    Hashes = hashValues.ToList(),
193                                    Bytes = bytes
194                                };
195             return treeHash;
196         }
197     }
198 }