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