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