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_ObjectID")]
77 public string ObjectID { get; set; }
79 [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")]
80 public string FilePath { get; set; }
83 public FileOverlayStatus OverlayStatus { get; set; }
86 public FileStatus FileStatus { get; set; }
89 public string ConflictReason { get; set; }
91 private string _checksum;
94 /// The tophash value of the file, calculated by using a Merkle hash with the SHA256 algorithm
97 /// The SHA256 algorithm is substantially more expenive than other algorithms.
98 /// Recalculating the Checksum should be avoided whenever possible.
101 public string Checksum
113 private string _shortHash;
116 /// An easy to calcualte hash over the entire file, used to detect file changes
118 /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
119 [Property(NotNull=true,Default="")]
120 public string ShortHash
134 public long? Version { get; set; }
137 public DateTime? VersionTimeStamp { get; set; }
141 public bool IsShared { get; set; }
144 public string SharedBy { get; set; }
147 public bool ShareWrite { get; set; }
150 public bool IsFolder{ get; set; }
152 [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)]
153 public IList<FileTag> Tags
155 get { return _tags; }
156 set { _tags = value; }
160 public DateTime Modified { get; set; }
163 public FileSystemInfo GetFileSystemInfo()
165 if (String.IsNullOrWhiteSpace(FilePath))
166 throw new InvalidOperationException();
167 Contract.EndContractBlock();
169 return Directory.Exists(FilePath) ?
170 (FileSystemInfo)new DirectoryInfo(FilePath)
171 : new FileInfo(FilePath);
174 public string GetRelativeUrl(AccountInfo accountInfo)
176 if (accountInfo==null)
177 throw new ArgumentNullException("accountInfo");
178 Contract.EndContractBlock();
180 var fsi=GetFileSystemInfo();
181 return fsi.AsRelativeUrlTo(accountInfo.AccountPath);
183 /*public static FileState FindByFilePath(string absolutePath)
185 if (string.IsNullOrWhiteSpace(absolutePath))
186 throw new ArgumentNullException("absolutePath");
187 Contract.EndContractBlock();
193 return Queryable.FirstOrDefault(s => s.FilePath == absolutePath);
197 Log.Error(ex.ToString());
204 /* public static void DeleteByFilePath(string absolutePath)
206 if (string.IsNullOrWhiteSpace(absolutePath))
207 throw new ArgumentNullException("absolutePath");
208 Contract.EndContractBlock();
210 ExecuteWithRetry((session, instance) =>
212 const string hqlDelete = "delete FileState where FilePath = :path";
213 var deletedEntities = session.CreateQuery(hqlDelete)
214 .SetString("path", absolutePath)
216 return deletedEntities;
221 public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
223 if (string.IsNullOrWhiteSpace(absolutePath))
224 throw new ArgumentNullException("absolutePath");
225 Contract.EndContractBlock();
227 ExecuteWithRetry((session, instance) =>
229 const string hqlUpdate = "update FileState set FileStatus= :status where FilePath = :path ";
230 var updatedEntities = session.CreateQuery(hqlUpdate)
231 .SetString("path", absolutePath)
232 .SetEnum("status", newStatus)
234 if (updatedEntities == 0)
236 var newState = new FileState
238 FilePath = absolutePath,
240 FileStatus = newStatus,
241 IsFolder=Directory.Exists(absolutePath)
243 newState.CreateAndFlush();
250 /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
252 if (string.IsNullOrWhiteSpace(absolutePath))
253 throw new ArgumentNullException("absolutePath");
254 Contract.EndContractBlock();
256 ExecuteWithRetry((session, instance) =>
258 const string hqlUpdate =
259 "update FileState set OverlayStatus= :status where FilePath = :path ";
260 var updatedEntities = session.CreateQuery(hqlUpdate)
261 .SetString("path", absolutePath)
262 .SetEnum("status", newStatus)
264 if (updatedEntities == 0)
266 var newState = new FileState
268 FilePath = absolutePath,
270 OverlayStatus = newStatus,
271 ShortHash = String.Empty,
272 IsFolder=Directory.Exists(absolutePath)
274 newState.CreateAndFlush();
281 public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string shortHash)
283 if (string.IsNullOrWhiteSpace(absolutePath))
284 throw new ArgumentNullException("absolutePath");
285 Contract.EndContractBlock();
287 ExecuteWithRetry((session, instance) =>
289 const string hqlUpdate =
290 "update FileState set OverlayStatus= :status where FilePath = :path ";
291 var updatedEntities = session.CreateQuery(hqlUpdate)
292 .SetString("path", absolutePath)
293 .SetEnum("status", newStatus)
295 if (updatedEntities == 0)
297 var newState = new FileState
299 FilePath = absolutePath,
301 OverlayStatus = newStatus,
302 ShortHash = shortHash??String.Empty,
303 IsFolder=Directory.Exists(absolutePath)
305 newState.CreateAndFlush();
313 public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
315 if (string.IsNullOrWhiteSpace(absolutePath))
316 throw new ArgumentNullException("absolutePath");
317 Contract.EndContractBlock();
319 ExecuteWithRetry((session, instance) =>
321 const string hqlUpdate =
322 "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path ";
323 var updatedEntities = session.CreateQuery(hqlUpdate)
324 .SetString("path", absolutePath)
325 .SetEnum("fileStatus", fileStatus)
326 .SetEnum("overlayStatus", overlayStatus)
328 return updatedEntities;
335 public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
337 if (string.IsNullOrWhiteSpace(absolutePath))
338 throw new ArgumentNullException("absolutePath");
339 Contract.EndContractBlock();
341 ExecuteWithRetry((session, instance) =>
343 const string hqlUpdate =
344 "update FileState set FileStatus= :fileStatus where FilePath = :path ";
345 var updatedEntities = session.CreateQuery(hqlUpdate)
346 .SetString("path", absolutePath)
347 .SetEnum("fileStatus", fileStatus)
349 return updatedEntities;
355 public static void RenameState(string oldPath, string newPath)
357 if (string.IsNullOrWhiteSpace(oldPath))
358 throw new ArgumentNullException("oldPath");
359 Contract.EndContractBlock();
361 ExecuteWithRetry((session, instance) =>
363 const string hqlUpdate =
364 "update FileState set FilePath= :newPath where FilePath = :oldPath ";
365 var updatedEntities = session.CreateQuery(hqlUpdate)
366 .SetString("oldPath", oldPath)
367 .SetString("newPath", newPath)
369 return updatedEntities;
374 /* public static void UpdateStatus(Guid id, FileStatus fileStatus)
377 ExecuteWithRetry((session, instance) =>
379 const string hqlUpdate =
380 "update FileState set FileStatus= :fileStatus where Id = :id ";
381 var updatedEntities = session.CreateQuery(hqlUpdate)
383 .SetEnum("fileStatus", fileStatus)
385 return updatedEntities;
389 public static void UpdateChecksum(string absolutePath, string shortHash, string checksum)
391 if (string.IsNullOrWhiteSpace(absolutePath))
392 throw new ArgumentNullException("absolutePath");
393 Contract.EndContractBlock();
395 ExecuteWithRetry((session, instance) =>
397 const string hqlUpdate = "update FileState set Checksum= :checksum,ShortHash=:shortHash where FilePath = :path ";
398 var updatedEntities = session.CreateQuery(hqlUpdate)
399 .SetString("path", absolutePath)
400 .SetString("checksum", checksum)
401 .SetString("shortHash", shortHash)
403 return updatedEntities;
408 public static void ChangeRootPath(string oldPath, string newPath)
410 if (String.IsNullOrWhiteSpace(oldPath))
411 throw new ArgumentNullException("oldPath");
412 if (!Path.IsPathRooted(oldPath))
413 throw new ArgumentException("oldPath must be an absolute path", "oldPath");
414 if (string.IsNullOrWhiteSpace(newPath))
415 throw new ArgumentNullException("newPath");
416 if (!Path.IsPathRooted(newPath))
417 throw new ArgumentException("newPath must be an absolute path", "newPath");
418 Contract.EndContractBlock();
420 //Ensure the paths end with the same character
421 if (!oldPath.EndsWith("\\"))
422 oldPath = oldPath + "\\";
423 if (!newPath.EndsWith("\\"))
424 newPath = newPath + "\\";
426 ExecuteWithRetry((session, instance) =>
428 const string hqlUpdate =
429 "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
430 var renames = session.CreateQuery(hqlUpdate)
431 .SetString("oldPath", oldPath)
432 .SetString("newPath", newPath)
438 public static FileState CreateFor(FileSystemInfo info)
441 throw new ArgumentNullException("info");
442 Contract.EndContractBlock();
444 if (info is DirectoryInfo)
447 FilePath = info.FullName,
448 OverlayStatus = FileOverlayStatus.Unversioned,
449 FileStatus = FileStatus.Created,
450 ShortHash=String.Empty,
455 var shortHash = ((FileInfo)info).ComputeShortHash();
456 var fileState = new FileState
458 FilePath = info.FullName,
459 OverlayStatus = FileOverlayStatus.Unversioned,
460 FileStatus = FileStatus.Created,
468 private static void ExecuteWithRetry(NHibernateDelegate call, object state)
474 using (new SessionScope())
476 Execute(call, state);
480 catch (ActiveRecordException )
489 /// Mark Unversioned all FileState rows from the database whose path
490 /// starts with one of the removed paths
492 /// <param name="removed"></param>
493 public static void UnversionPaths(List<string> removed)
497 if (removed.Count == 0)
500 //Create a disjunction (list of OR statements
501 var disjunction = new Disjunction();
502 foreach (var path in removed)
504 //with the restriction FileState.FilePath like '@path%'
505 disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)
506 .IsLike(path, MatchMode.Start));
509 //Generate a query from the disjunction
510 var query=QueryOver.Of<FileState>().Where(disjunction);
512 ExecuteWithRetry((session,instance)=>
514 using (var t=session.BeginTransaction())
516 var states = query.GetExecutableQueryOver(session).List();
517 foreach (var state in states)
519 state.FileStatus = FileStatus.Unversioned;
520 state.OverlayStatus = FileOverlayStatus.Unversioned;
531 [ActiveRecord("Tags")]
532 public class FileTag : ActiveRecordLinqBase<FileTag>
535 public int Id { get; set; }
538 public string Name { get; set; }
541 public string Value { get; set; }
543 [BelongsTo("FileStateId")]
544 public FileState FileState { get; set; }