2 /* -----------------------------------------------------------------------
3 * <copyright file="FileState.cs" company="GRNet">
5 * Copyright 2011-2012 GRNET S.A. All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
11 * 1. Redistributions of source code must retain the above
12 * copyright notice, this list of conditions and the following
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.
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.
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.
39 * -----------------------------------------------------------------------
42 using System.Diagnostics.Contracts;
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;
58 using System.Collections.Generic;
61 /// TODO: Update summary.
64 public class FileState : ActiveRecordLinqBase<FileState>
66 private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
69 private IList<FileTag> _tags = new List<FileTag>();
71 [PrimaryKey(PrimaryKeyType.Guid)]
72 public Guid Id { get; set; }
75 [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")]
76 public string FilePath { get; set; }
79 public FileOverlayStatus OverlayStatus { get; set; }
82 public FileStatus FileStatus { get; set; }
84 private string _checksum;
87 /// The tophash value of the file, calculated by using a Merkle hash with the SHA256 algorithm
90 /// The SHA256 algorithm is substantially more expenive than other algorithms.
91 /// Recalculating the Checksum should be avoided whenever possible.
94 public string Checksum
106 private string _shortHash;
109 /// An easy to calcualte hash over the entire file, used to detect file changes
111 /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
112 [Property(NotNull=true,Default="")]
113 public string ShortHash
127 public long? Version { get; set; }
130 public DateTime? VersionTimeStamp { get; set; }
134 public bool IsShared { get; set; }
137 public string SharedBy { get; set; }
140 public bool ShareWrite { get; set; }
143 public bool IsFolder{ get; set; }
145 [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)]
146 public IList<FileTag> Tags
148 get { return _tags; }
149 set { _tags = value; }
153 public DateTime Modified { get; set; }
156 public FileSystemInfo GetFileSystemInfo()
158 if (String.IsNullOrWhiteSpace(FilePath))
159 throw new InvalidOperationException();
160 Contract.EndContractBlock();
162 return Directory.Exists(FilePath) ?
163 (FileSystemInfo)new DirectoryInfo(FilePath)
164 : new FileInfo(FilePath);
167 public string GetRelativeUrl(AccountInfo accountInfo)
169 if (accountInfo==null)
170 throw new ArgumentNullException("accountInfo");
171 Contract.EndContractBlock();
173 var fsi=GetFileSystemInfo();
174 return fsi.AsRelativeUrlTo(accountInfo.AccountPath);
176 /*public static FileState FindByFilePath(string absolutePath)
178 if (string.IsNullOrWhiteSpace(absolutePath))
179 throw new ArgumentNullException("absolutePath");
180 Contract.EndContractBlock();
186 return Queryable.FirstOrDefault(s => s.FilePath == absolutePath);
190 Log.Error(ex.ToString());
197 /* public static void DeleteByFilePath(string absolutePath)
199 if (string.IsNullOrWhiteSpace(absolutePath))
200 throw new ArgumentNullException("absolutePath");
201 Contract.EndContractBlock();
203 ExecuteWithRetry((session, instance) =>
205 const string hqlDelete = "delete FileState where FilePath = :path";
206 var deletedEntities = session.CreateQuery(hqlDelete)
207 .SetString("path", absolutePath)
209 return deletedEntities;
214 public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
216 if (string.IsNullOrWhiteSpace(absolutePath))
217 throw new ArgumentNullException("absolutePath");
218 Contract.EndContractBlock();
220 ExecuteWithRetry((session, instance) =>
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)
227 if (updatedEntities == 0)
229 var newState = new FileState
231 FilePath = absolutePath,
233 FileStatus = newStatus,
234 IsFolder=Directory.Exists(absolutePath)
236 newState.CreateAndFlush();
243 /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
245 if (string.IsNullOrWhiteSpace(absolutePath))
246 throw new ArgumentNullException("absolutePath");
247 Contract.EndContractBlock();
249 ExecuteWithRetry((session, instance) =>
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)
257 if (updatedEntities == 0)
259 var newState = new FileState
261 FilePath = absolutePath,
263 OverlayStatus = newStatus,
264 ShortHash = String.Empty,
265 IsFolder=Directory.Exists(absolutePath)
267 newState.CreateAndFlush();
274 public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string shortHash)
276 if (string.IsNullOrWhiteSpace(absolutePath))
277 throw new ArgumentNullException("absolutePath");
278 Contract.EndContractBlock();
280 ExecuteWithRetry((session, instance) =>
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)
288 if (updatedEntities == 0)
290 var newState = new FileState
292 FilePath = absolutePath,
294 OverlayStatus = newStatus,
295 ShortHash = shortHash??String.Empty,
296 IsFolder=Directory.Exists(absolutePath)
298 newState.CreateAndFlush();
306 public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
308 if (string.IsNullOrWhiteSpace(absolutePath))
309 throw new ArgumentNullException("absolutePath");
310 Contract.EndContractBlock();
312 ExecuteWithRetry((session, instance) =>
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)
321 return updatedEntities;
328 public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
330 if (string.IsNullOrWhiteSpace(absolutePath))
331 throw new ArgumentNullException("absolutePath");
332 Contract.EndContractBlock();
334 ExecuteWithRetry((session, instance) =>
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)
342 return updatedEntities;
348 public static void RenameState(string oldPath, string newPath)
350 if (string.IsNullOrWhiteSpace(oldPath))
351 throw new ArgumentNullException("oldPath");
352 Contract.EndContractBlock();
354 ExecuteWithRetry((session, instance) =>
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)
362 return updatedEntities;
367 /* public static void UpdateStatus(Guid id, FileStatus fileStatus)
370 ExecuteWithRetry((session, instance) =>
372 const string hqlUpdate =
373 "update FileState set FileStatus= :fileStatus where Id = :id ";
374 var updatedEntities = session.CreateQuery(hqlUpdate)
376 .SetEnum("fileStatus", fileStatus)
378 return updatedEntities;
382 public static void UpdateChecksum(string absolutePath, string shortHash, string checksum)
384 if (string.IsNullOrWhiteSpace(absolutePath))
385 throw new ArgumentNullException("absolutePath");
386 Contract.EndContractBlock();
388 ExecuteWithRetry((session, instance) =>
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)
396 return updatedEntities;
401 public static void ChangeRootPath(string oldPath, string newPath)
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();
413 //Ensure the paths end with the same character
414 if (!oldPath.EndsWith("\\"))
415 oldPath = oldPath + "\\";
416 if (!newPath.EndsWith("\\"))
417 newPath = newPath + "\\";
419 ExecuteWithRetry((session, instance) =>
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)
431 public static FileState CreateFor(FileSystemInfo info)
434 throw new ArgumentNullException("info");
435 Contract.EndContractBlock();
437 if (info is DirectoryInfo)
440 FilePath = info.FullName,
441 OverlayStatus = FileOverlayStatus.Unversioned,
442 FileStatus = FileStatus.Created,
443 ShortHash=String.Empty,
448 var shortHash = ((FileInfo)info).ComputeShortHash();
449 var fileState = new FileState
451 FilePath = info.FullName,
452 OverlayStatus = FileOverlayStatus.Unversioned,
453 FileStatus = FileStatus.Created,
461 private static void ExecuteWithRetry(NHibernateDelegate call, object state)
467 using (new SessionScope())
469 Execute(call, state);
473 catch (ActiveRecordException )
482 /// Mark Unversioned all FileState rows from the database whose path
483 /// starts with one of the removed paths
485 /// <param name="removed"></param>
486 public static void UnversionPaths(List<string> removed)
490 if (removed.Count == 0)
493 //Create a disjunction (list of OR statements
494 var disjunction = new Disjunction();
495 foreach (var path in removed)
497 //with the restriction FileState.FilePath like '@path%'
498 disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)
499 .IsLike(path, MatchMode.Start));
502 //Generate a query from the disjunction
503 var query=QueryOver.Of<FileState>().Where(disjunction);
505 ExecuteWithRetry((session,instance)=>
507 using (var t=session.BeginTransaction())
509 var states = query.GetExecutableQueryOver(session).List();
510 foreach (var state in states)
512 state.FileStatus = FileStatus.Unversioned;
513 state.OverlayStatus = FileOverlayStatus.Unversioned;
524 [ActiveRecord("Tags")]
525 public class FileTag : ActiveRecordLinqBase<FileTag>
528 public int Id { get; set; }
531 public string Name { get; set; }
534 public string Value { get; set; }
536 [BelongsTo("FileStateId")]
537 public FileState FileState { get; set; }