5e2ef2756967183278a9380967808adc360390c6
[pithos-ms-client] / trunk / Pithos.Core / FileState.cs
1 #region
2 /* -----------------------------------------------------------------------
3  * <copyright file="FileState.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.Diagnostics.Contracts;
43 using System.IO;
44 using System.Reflection;
45 using System.Threading.Tasks;
46 using Castle.ActiveRecord;
47 using Castle.ActiveRecord.Framework;
48 using Castle.ActiveRecord.Queries;
49 using NHibernate.Criterion;
50 using Pithos.Core.Agents;
51 using Pithos.Interfaces;
52 using Pithos.Network;
53 using log4net;
54
55 namespace Pithos.Core
56 {
57     using System;
58     using System.Collections.Generic;    
59
60     /// <summary>
61     /// TODO: Update summary.
62     /// </summary>
63     [ActiveRecord]
64     public class FileState : ActiveRecordLinqBase<FileState>
65     {
66         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
67
68
69         private IList<FileTag> _tags = new List<FileTag>();
70
71         [PrimaryKey(PrimaryKeyType.Guid)]
72         public Guid Id { get; set; }
73
74         
75         //[Property(Unique = true, UniqueKey = "IX_FileState_ObjectID")]
76         public string ObjectID { get; set; }
77
78         [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")]
79         public string FilePath { get; set; }
80
81         [Property]
82         public FileOverlayStatus OverlayStatus { get; set; }
83
84         [Property]
85         public FileStatus FileStatus { get; set; }
86
87         [Property]
88         public string ConflictReason { get; set; }
89
90         private string _checksum;
91
92         /// <summary>
93         /// The tophash value of the file, calculated by using a Merkle hash with the SHA256 algorithm
94         /// </summary>
95         /// <remarks>
96         /// The SHA256 algorithm is substantially more expenive than other algorithms.
97         /// Recalculating the Checksum should be avoided whenever possible.
98         /// </remarks>
99         [Property]
100         public string Checksum
101         {
102             get
103             {
104                 return _checksum;
105             }
106             set
107             {
108                 _checksum = value;
109             }
110         }
111
112         private string _shortHash;
113
114         /// <summary>
115         /// An easy to calcualte hash over the entire file, used to detect file changes
116         /// </summary>
117         /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
118         [Property(NotNull=true,Default="")]
119         public string ShortHash
120         {
121             get
122             {
123                 return _shortHash;
124             }
125             set
126             {
127                 _shortHash = value;
128             }
129         }
130
131
132         [Property]
133         public long? Version { get; set; }
134
135         [Property]
136         public DateTime? VersionTimeStamp { get; set; }
137
138
139         [Property]
140         public bool IsShared { get; set; }
141
142         [Property]
143         public string SharedBy { get; set; }
144
145         [Property]
146         public bool ShareWrite { get; set; }
147
148         [Property]
149         public bool IsFolder{ get; set; }
150
151         [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)]
152         public IList<FileTag> Tags
153         {
154             get { return _tags; }
155             set { _tags = value; }
156         }
157
158         [Property]
159         public DateTime Modified { get; set; }
160
161
162         public FileSystemInfo GetFileSystemInfo()
163         {
164             if (String.IsNullOrWhiteSpace(FilePath))
165                 throw new InvalidOperationException();
166             Contract.EndContractBlock();
167
168             return Directory.Exists(FilePath) ?
169                 (FileSystemInfo)new DirectoryInfo(FilePath)
170                 : new FileInfo(FilePath);
171         }
172
173         public string GetRelativeUrl(AccountInfo accountInfo)
174         {
175             if (accountInfo==null)
176                 throw new ArgumentNullException("accountInfo");
177             Contract.EndContractBlock();
178
179             var fsi=GetFileSystemInfo();
180             return fsi.AsRelativeUrlTo(accountInfo.AccountPath);
181         }
182         /*public static FileState FindByFilePath(string absolutePath)
183         {
184             if (string.IsNullOrWhiteSpace(absolutePath))
185                 throw new ArgumentNullException("absolutePath");
186             Contract.EndContractBlock();
187             try
188             {
189
190                 
191
192                 return Queryable.FirstOrDefault(s => s.FilePath == absolutePath);
193             }
194             catch (Exception ex)
195             {
196                 Log.Error(ex.ToString());
197                 throw;
198             }
199
200
201         }*/
202
203        /* public static void DeleteByFilePath(string absolutePath)
204         {
205             if (string.IsNullOrWhiteSpace(absolutePath))
206                 throw new ArgumentNullException("absolutePath");
207             Contract.EndContractBlock();
208
209             ExecuteWithRetry((session, instance) =>
210                         {
211                             const string hqlDelete = "delete FileState where FilePath = :path";
212                             var deletedEntities = session.CreateQuery(hqlDelete)
213                                 .SetString("path", absolutePath)
214                                 .ExecuteUpdate();
215                             return deletedEntities;
216                         }, null);
217
218         }*/
219
220         public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
221         {
222             if (string.IsNullOrWhiteSpace(absolutePath))
223                 throw new ArgumentNullException("absolutePath");
224             Contract.EndContractBlock();
225
226             ExecuteWithRetry((session, instance) =>
227                         {
228                             const string hqlUpdate = "update FileState set FileStatus= :status where FilePath = :path ";
229                             var updatedEntities = session.CreateQuery(hqlUpdate)
230                                 .SetString("path", absolutePath)
231                                 .SetEnum("status", newStatus)
232                                 .ExecuteUpdate();
233                             if (updatedEntities == 0)
234                             {
235                                 var newState = new FileState
236                                                    {
237                                                        FilePath = absolutePath,
238                                                        Id = Guid.NewGuid(),
239                                                        FileStatus = newStatus,
240                                                        IsFolder=Directory.Exists(absolutePath)
241                                                    };
242                                 newState.CreateAndFlush();
243                             }
244                             return null;
245                         }, null);
246
247         }
248
249         /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
250         {
251             if (string.IsNullOrWhiteSpace(absolutePath))
252                 throw new ArgumentNullException("absolutePath");
253             Contract.EndContractBlock();
254
255             ExecuteWithRetry((session, instance) =>
256                         {
257                             const string hqlUpdate =
258                                 "update FileState set OverlayStatus= :status where FilePath = :path ";
259                             var updatedEntities = session.CreateQuery(hqlUpdate)                                
260                                 .SetString("path", absolutePath)
261                                 .SetEnum("status", newStatus)                                
262                                 .ExecuteUpdate();
263                             if (updatedEntities == 0)
264                             {
265                                 var newState = new FileState
266                                                    {
267                                                        FilePath = absolutePath,
268                                                        Id = Guid.NewGuid(),
269                                                        OverlayStatus = newStatus,
270                                                        ShortHash = String.Empty,
271                                                        IsFolder=Directory.Exists(absolutePath)
272                                                    };
273                                 newState.CreateAndFlush();
274                             }
275                             return null;
276                         }, null);
277
278         }
279 */
280         public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string shortHash)
281         {
282             if (string.IsNullOrWhiteSpace(absolutePath))
283                 throw new ArgumentNullException("absolutePath");
284             Contract.EndContractBlock();
285
286             ExecuteWithRetry((session, instance) =>
287                         {
288                             const string hqlUpdate =
289                                 "update FileState set OverlayStatus= :status where FilePath = :path ";
290                             var updatedEntities = session.CreateQuery(hqlUpdate)                                
291                                 .SetString("path", absolutePath)
292                                 .SetEnum("status", newStatus)                                
293                                 .ExecuteUpdate();
294                             if (updatedEntities == 0)
295                             {
296                                 var newState = new FileState
297                                                    {
298                                                        FilePath = absolutePath,
299                                                        Id = Guid.NewGuid(),
300                                                        OverlayStatus = newStatus,
301                                                        ShortHash = shortHash??String.Empty,
302                                                        IsFolder=Directory.Exists(absolutePath)
303                                                    };
304                                 newState.CreateAndFlush();
305                             }
306                             return null;
307                         }, null);
308
309         }
310
311 /*
312         public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
313         {
314             if (string.IsNullOrWhiteSpace(absolutePath))
315                 throw new ArgumentNullException("absolutePath");
316             Contract.EndContractBlock();
317
318             ExecuteWithRetry((session, instance) =>
319                         {
320                             const string hqlUpdate =
321                                 "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path  ";
322                             var updatedEntities = session.CreateQuery(hqlUpdate)
323                                 .SetString("path", absolutePath)
324                                 .SetEnum("fileStatus", fileStatus)
325                                 .SetEnum("overlayStatus", overlayStatus)
326                                 .ExecuteUpdate();
327                             return updatedEntities;
328                         }, null);
329
330         }
331 */
332
333 /*
334         public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
335         {
336             if (string.IsNullOrWhiteSpace(absolutePath))
337                 throw new ArgumentNullException("absolutePath");
338             Contract.EndContractBlock();
339
340             ExecuteWithRetry((session, instance) =>
341                         {
342                             const string hqlUpdate =
343                                 "update FileState set FileStatus= :fileStatus where FilePath = :path  ";
344                             var updatedEntities = session.CreateQuery(hqlUpdate)
345                                 .SetString("path", absolutePath)
346                                 .SetEnum("fileStatus", fileStatus)
347                                 .ExecuteUpdate();
348                             return updatedEntities;
349                         }, null);
350
351         }
352
353 */
354         public static void RenameState(string oldPath, string newPath)
355         {
356             if (string.IsNullOrWhiteSpace(oldPath))
357                 throw new ArgumentNullException("oldPath");
358             Contract.EndContractBlock();
359
360             ExecuteWithRetry((session, instance) =>
361                         {
362                             const string hqlUpdate =
363                                 "update FileState set FilePath= :newPath where FilePath = :oldPath ";
364                             var updatedEntities = session.CreateQuery(hqlUpdate)
365                                 .SetString("oldPath", oldPath)
366                                 .SetString("newPath", newPath)
367                                 .ExecuteUpdate();
368                             return updatedEntities;
369                         }, null);
370
371         }
372
373      /*   public static void UpdateStatus(Guid id, FileStatus fileStatus)
374         {
375
376             ExecuteWithRetry((session, instance) =>
377             {
378                 const string hqlUpdate =
379                     "update FileState set FileStatus= :fileStatus where Id = :id  ";
380                 var updatedEntities = session.CreateQuery(hqlUpdate)
381                     .SetGuid("id", id)
382                     .SetEnum("fileStatus", fileStatus)
383                     .ExecuteUpdate();
384                 return updatedEntities;
385             }, null);
386         }*/
387
388         public static void UpdateChecksum(string absolutePath, string shortHash, string checksum)
389         {
390             if (string.IsNullOrWhiteSpace(absolutePath))
391                 throw new ArgumentNullException("absolutePath");
392             Contract.EndContractBlock();
393
394             ExecuteWithRetry((session, instance) =>
395                         {
396                             const string hqlUpdate = "update FileState set Checksum= :checksum,ShortHash=:shortHash where FilePath = :path ";
397                             var updatedEntities = session.CreateQuery(hqlUpdate)
398                                 .SetString("path", absolutePath)
399                                 .SetString("checksum", checksum)
400                                 .SetString("shortHash", shortHash)
401                                 .ExecuteUpdate();
402                             return updatedEntities;
403                         }, null);
404
405         }
406
407         public static void ChangeRootPath(string oldPath, string newPath)
408         {
409             if (String.IsNullOrWhiteSpace(oldPath))
410                 throw new ArgumentNullException("oldPath");
411             if (!Path.IsPathRooted(oldPath))
412                 throw new ArgumentException("oldPath must be an absolute path", "oldPath");
413             if (string.IsNullOrWhiteSpace(newPath))
414                 throw new ArgumentNullException("newPath");
415             if (!Path.IsPathRooted(newPath))
416                 throw new ArgumentException("newPath must be an absolute path", "newPath");
417             Contract.EndContractBlock();
418
419             //Ensure the paths end with the same character
420             if (!oldPath.EndsWith("\\"))
421                 oldPath = oldPath + "\\";
422             if (!newPath.EndsWith("\\"))
423                 newPath = newPath + "\\";
424
425                 ExecuteWithRetry((session, instance) =>
426                             {
427                                 const string hqlUpdate =
428                                     "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
429                                 var renames = session.CreateQuery(hqlUpdate)
430                                     .SetString("oldPath", oldPath)
431                                     .SetString("newPath", newPath)
432                                     .ExecuteUpdate();
433                                 return renames;
434                             }, null);
435         }
436
437         public static FileState CreateFor(FileSystemInfo info)
438         {
439             if(info==null)
440                 throw new ArgumentNullException("info");
441             Contract.EndContractBlock();
442             
443             if (info is DirectoryInfo)
444                 return new FileState
445                 {
446                     FilePath = info.FullName,
447                     OverlayStatus = FileOverlayStatus.Unversioned,
448                     FileStatus = FileStatus.Created,
449                     ShortHash=String.Empty,
450                     Id = Guid.NewGuid()
451                 };
452
453
454             var shortHash = ((FileInfo)info).ComputeShortHash();
455             var fileState = new FileState
456                                 {
457                                     FilePath = info.FullName,
458                                     OverlayStatus = FileOverlayStatus.Unversioned,
459                                     FileStatus = FileStatus.Created,               
460                                     ShortHash=shortHash,
461                                     Id = Guid.NewGuid()
462                                 };
463             return fileState;
464         }
465
466
467         private static void ExecuteWithRetry(NHibernateDelegate call, object state)
468         {
469             int retries = 3;
470             while (retries > 0)
471                 try
472                 {
473                     using (new SessionScope())
474                     {
475                         Execute(call, state);
476                         return;
477                     }
478                 }
479                 catch (ActiveRecordException )
480                 {
481                     retries--;
482                     if (retries <= 0)
483                         throw;
484                 }
485         }
486
487         /// <summary>
488         /// Mark Unversioned all FileState rows from the database whose path
489         /// starts with one of the removed paths
490         /// </summary>
491         /// <param name="removed"></param>
492         public static void UnversionPaths(List<string> removed)
493         {
494             if (removed == null)
495                 return;
496             if (removed.Count == 0)
497                 return;
498
499             //Create a disjunction (list of OR statements
500             var disjunction = new Disjunction();            
501             foreach (var path in removed)
502             {
503                 //with the restriction FileState.FilePath like '@path%'
504                 disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)
505                     .IsLike(path, MatchMode.Start));
506             }
507
508             //Generate a query from the disjunction
509             var query=QueryOver.Of<FileState>().Where(disjunction);
510                         
511             ExecuteWithRetry((session,instance)=>
512                                  {
513                                      using (var t=session.BeginTransaction())
514                                      {
515                                          var states = query.GetExecutableQueryOver(session).List();
516                                          foreach (var state in states)
517                                          {
518                                              state.FileStatus = FileStatus.Unversioned;
519                                              state.OverlayStatus = FileOverlayStatus.Unversioned;
520                                              state.Update();
521                                          }
522                                          t.Commit();
523                                      }
524                                      return null;
525                                  },null);
526         }
527
528     }
529
530     [ActiveRecord("Tags")]
531     public class FileTag : ActiveRecordLinqBase<FileTag>
532     {
533         [PrimaryKey]
534         public int Id { get; set; }
535
536         [Property]
537         public string Name { get; set; }
538
539         [Property]
540         public string Value { get; set; }
541
542         [BelongsTo("FileStateId")]
543         public FileState FileState { get; set; }
544
545     }
546    
547 }