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