Using MD5 to quickly check for local modifications before calculating the expensive...
[pithos-ms-client] / trunk / Pithos.Interfaces / ObjectInfo.cs
1 #region
2 /* -----------------------------------------------------------------------
3  * <copyright file="ObjectInfo.cs" company="GRNet">
4  * 
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or
8  * without modification, are permitted provided that the following
9  * conditions are met:
10  *
11  *   1. Redistributions of source code must retain the above
12  *      copyright notice, this list of conditions and the following
13  *      disclaimer.
14  *
15  *   2. Redistributions in binary form must reproduce the above
16  *      copyright notice, this list of conditions and the following
17  *      disclaimer in the documentation and/or other materials
18  *      provided with the distribution.
19  *
20  *
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * The views and conclusions contained in the software and
35  * documentation are those of the authors and should not be
36  * interpreted as representing official policies, either expressed
37  * or implied, of GRNET S.A.
38  * </copyright>
39  * -----------------------------------------------------------------------
40  */
41 #endregion
42 using System;
43 using System.Collections.Generic;
44 using System.Diagnostics;
45 using System.Diagnostics.Contracts;
46 using System.Dynamic;
47 using System.Globalization;
48 using System.IO;
49 using System.Linq;
50 using System.Text;
51 using Newtonsoft.Json;
52
53 namespace Pithos.Interfaces
54 {
55     [DebuggerDisplay("Name {Name}")]
56     public class ObjectInfo//:DynamicObject 
57     {
58         private readonly List<string> _knownContainers= new List<string>{"trash"};
59         public string Name { get; set; }
60
61         [JsonProperty("hash")]
62         public string ETag { get; set; }
63
64         //public string Hash { get; set; }
65
66 /*
67         public string X_Object_Hash { get { return Hash; } set { Hash = value; } }
68 */
69
70         public string X_Object_Hash { get; set; }
71
72         [JsonProperty("x_object_uuid")]
73         public string UUID { get; set; }
74
75         public long Bytes { get; set; }
76         public string Content_Type { get; set; }
77         public DateTime Last_Modified { get; set; }
78
79         private Dictionary<string, string> _tags=new Dictionary<string, string>();
80         public Dictionary<string, string> Tags
81         {
82             get { return _tags; }
83             set { _tags = value; }
84         }
85
86         private Dictionary<string, string> _extensions=new Dictionary<string, string>();
87         public Dictionary<string, string> Extensions
88         {
89             get { return _extensions; }
90             set
91             {
92                 _extensions = value;
93                 ExtractKnownExtensions();
94             }
95         }
96         
97         
98         private Dictionary<string, string> _permissions=new Dictionary<string, string>();
99         [JsonProperty("x_object_sharing")]
100         [JsonConverter(typeof(PermissionConverter))]
101         public Dictionary<string, string> Permissions
102         {
103             get { return _permissions; }
104             set
105             {
106                 _permissions = value;                
107             }
108         }
109
110         /// <summary>
111         /// Version number
112         /// </summary>
113         [JsonProperty("x_object_version")]
114         public long? Version { get; set; }
115
116
117         /// <summary>
118         /// Shared object permissions can be Read or Write
119         /// </summary>
120         [JsonProperty("x_object_allowed_to")]
121         public string AllowedTo { get; set; }
122
123
124         /// <summary>
125         /// Version timestamp
126         /// </summary>
127         [JsonProperty("X_Object_Version_Timestamp"), JsonConverter(typeof(PithosDateTimeConverter))]
128         public DateTime? VersionTimestamp { get; set; }
129
130         [JsonProperty("X_Object_Modified_By")]
131         public string ModifiedBy { get; set; }
132
133
134         public Stream Stream { get; set; }
135
136
137         public Uri StorageUri { get; set; }
138
139         public string Account { get; set; }
140
141         public string Container { get; set; }
142
143         public Uri Uri
144         {
145             get
146             {
147                 var relativeUrl=String.Format("{0}/{1}/{2}",Account, Container,Name);
148                 return StorageUri==null 
149                     ? new Uri(relativeUrl,UriKind.Relative) 
150                     : new Uri(StorageUri, relativeUrl);
151             }
152         }
153
154         public string ContendDisposition { get; set; }
155
156         public string ContentEncoding { get; set; }
157
158         public string Manifest { get; set; }
159         
160         public bool IsPublic
161         {
162             get { return !String.IsNullOrWhiteSpace(PublicUrl); }
163             set
164             {
165                 if (!value)
166                     PublicUrl = null;
167                 else if (String.IsNullOrWhiteSpace(PublicUrl))
168                     PublicUrl="true";                
169             }
170         }
171
172         [JsonProperty("X_Object_Public")]
173         public string PublicUrl { get; set; }
174
175         public string PreviousHash { get; set; }
176
177         public ObjectInfo()
178         {}
179
180         public ObjectInfo(string accountPath,string accountName,FileSystemInfo fileInfo)
181         {
182             if (String.IsNullOrWhiteSpace(accountPath))
183                 throw new ArgumentNullException("accountPath");
184             if (string.IsNullOrWhiteSpace(accountName))
185                 throw new ArgumentNullException("accountName");
186             if (fileInfo == null)
187                 throw new ArgumentNullException("fileInfo");
188             Contract.EndContractBlock();
189
190             var relativeUrl = fileInfo.WithProperCapitalization().AsRelativeUrlTo(accountPath);
191             //The first part of the URL is the container
192             var slashIndex = relativeUrl.IndexOf('/');
193             var container = relativeUrl.Substring(0, slashIndex);
194             //The second is the file's url relative to the container
195             var fileUrl = relativeUrl.Substring(slashIndex + 1);
196
197             Account = accountName;
198             Container = container;
199             Name = fileUrl; 
200         }
201
202
203         private void ExtractKnownExtensions()
204         {
205             Version=GetLong(KnownExtensions.X_Object_Version);
206             VersionTimestamp = GetTimestamp(KnownExtensions.X_Object_Version_Timestamp);
207             ModifiedBy = GetString(KnownExtensions.X_Object_Modified_By);
208         }
209
210         private string GetString(string name)
211         {            
212             string value;
213             _extensions.TryGetValue(name, out value);
214             return value ;                        
215         }
216         
217         private long? GetLong(string name)
218         {
219             string version;
220             long value;
221             return _extensions.TryGetValue(name, out version) && long.TryParse(version, out value)
222                        ? (long?) value
223                        : null;
224         }
225
226         private DateTime? GetTimestamp(string name)
227         {
228             string version;
229             DateTime value;
230             if (_extensions.TryGetValue(name, out version) && 
231                 DateTime.TryParse(version,CultureInfo.InvariantCulture,DateTimeStyles.AdjustToUniversal, out value))
232             {
233                 return value;
234             }
235             return null;
236         }
237
238
239         public static ObjectInfo Empty = new ObjectInfo
240         {
241             Name = String.Empty,
242             ETag= String.Empty,
243             X_Object_Hash= String.Empty,
244             Bytes = 0,
245             Content_Type = String.Empty,
246             Last_Modified = DateTime.MinValue,
247             Exists=false
248         };
249
250         private bool _exists=true;
251
252         public bool Exists
253         {
254             get {
255                 return _exists;
256             }
257             set {
258                 _exists = value;
259             }
260         }
261
262
263         public string RelativeUrlToFilePath(string currentAccount)
264         {
265             if (Name==null)
266                 throw new InvalidOperationException("Name can't be null");
267             if (String.IsNullOrWhiteSpace(currentAccount))
268                 throw new ArgumentNullException("currentAccount");
269             Contract.EndContractBlock();
270
271             if (this == Empty)
272                 return String.Empty;
273
274             var unescaped = Uri.UnescapeDataString(Name);
275             var path = unescaped.Replace("/", "\\");
276             var pathParts=new Stack<string>();
277             pathParts.Push(path);
278             if (!String.IsNullOrWhiteSpace(Container) && !_knownContainers.Contains(Container))
279                 pathParts.Push(Container);
280             if (!currentAccount.Equals(Account, StringComparison.InvariantCultureIgnoreCase))
281             {
282                 if (Account != null)
283                 {
284                     pathParts.Push(Account);
285                     pathParts.Push(FolderConstants.OthersFolder);
286                 }
287             }
288             var finalPath=Path.Combine(pathParts.ToArray());
289             return finalPath;
290         }
291
292 /*
293         public override bool TrySetMember(SetMemberBinder binder, object value)
294         {
295             if (binder.Name.StartsWith("x_object_meta"))
296             {
297                 Tags[binder.Name] = value.ToString();
298             }
299             return false;
300         }
301 */
302
303         public string GetPermissionString()
304         {
305             if (Permissions==null)
306                 throw new InvalidOperationException();
307             Contract.EndContractBlock();
308
309             if (Permissions.Count == 0)
310                 return "~";
311             var permissionBuilder = new StringBuilder();
312             var groupings = Permissions.GroupBy(pair => pair.Value.Trim(), pair => pair.Key.Trim());
313             foreach (var grouping in groupings)
314             {
315                 permissionBuilder.AppendFormat("{0}={1};", grouping.Key, String.Join(",", grouping));
316             }
317             var permissions = Uri.EscapeDataString(permissionBuilder.ToString().Trim(';'));
318             return permissions;
319         }
320
321         public void SetPermissions(string permissions)
322         {
323             if (String.IsNullOrWhiteSpace(permissions))
324                 return;
325             
326             Permissions = PermissionConverter.ParsePermissions(permissions);
327         }
328
329         //The previous values that correspond to a NoModification object
330         //have the same account, container and possibly the same folder
331         public bool CorrespondsTo(ObjectInfo other)
332         {
333             return other.Account == this.Account
334                    && other.Container == this.Container
335                    && (this.Name == null || other.Name.StartsWith(this.Name));
336         }
337
338         public bool IsWritable(string account)
339         {
340             //If the Allowed To header has no value, try to determine the Share permissions
341             if (AllowedTo == null)
342             {
343                 //If this file has no permissions defined, we can probably write it
344                 //This case should occur only when the info comes from a listing of the user's own files
345                 if (Permissions == null || Permissions.Count == 0)
346                     return true;
347                 string perms;
348
349                 //Do we have an explicit write share to this account?
350                 return Permissions.TryGetValue(account, out perms) 
351                     && perms.Equals("write",StringComparison.InvariantCultureIgnoreCase);
352                 
353             }
354             //Otherwise return the permissions specified by AllowedTo
355             return AllowedTo.Equals("write",StringComparison.InvariantCultureIgnoreCase) ;
356         }
357
358         public ObjectInfo Previous { get; private set; }
359
360         public bool IsDirectory
361         {
362             get
363             {
364                 if (Content_Type == null)
365                     return false;
366                 if (Content_Type.StartsWith(@"application/directory",StringComparison.InvariantCultureIgnoreCase))
367                     return true;
368                 if (Content_Type.StartsWith(@"application/folder",StringComparison.InvariantCultureIgnoreCase))
369                     return true;
370                 return false;
371             }
372         }
373
374         public Uri AccountKey
375         {
376             get { return new Uri(StorageUri,"../" + Account); }
377         }
378
379         public ObjectInfo SetPrevious(ObjectInfo previous)
380         {            
381             Previous = previous;
382             PreviousHash = previous.X_Object_Hash;
383             return this;
384         }
385
386         public bool? IsShared
387         {
388             get
389             {
390                 if (Uri == null || StorageUri == null)
391                     return null;
392                 var isShared = !Uri.ToString().StartsWith(StorageUri.ToString());
393                 return isShared;
394             }
395         }
396     }
397 }