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
114 /// An easy to calcualte hash over the entire file, used to detect file changes
116 /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
117 [Property(NotNull = true, Default = "")]
118 public string ETag { get; set; }
120 [Property(NotNull = true, Default = "")]
121 public string LastMD5 { get; set; }
124 public DateTime? LastWriteDate { get; set; }
127 public long? LastLength { get; set; }
130 public long? Version { get; set; }
133 public DateTime? VersionTimeStamp { get; set; }
137 public bool IsShared { get; set; }
140 public string SharedBy { get; set; }
143 public bool ShareWrite { get; set; }
146 public bool IsFolder{ get; set; }
148 [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)]
149 public IList<FileTag> Tags
151 get { return _tags; }
152 set { _tags = value; }
156 public DateTime Modified { get; set; }
159 public FileSystemInfo GetFileSystemInfo()
161 if (String.IsNullOrWhiteSpace(FilePath))
162 throw new InvalidOperationException();
163 Contract.EndContractBlock();
165 return Directory.Exists(FilePath) ?
166 (FileSystemInfo)new DirectoryInfo(FilePath)
167 : new FileInfo(FilePath);
170 public string GetRelativeUrl(AccountInfo accountInfo)
172 if (accountInfo==null)
173 throw new ArgumentNullException("accountInfo");
174 Contract.EndContractBlock();
176 var fsi=GetFileSystemInfo();
177 return fsi.AsRelativeUrlTo(accountInfo.AccountPath);
179 /*public static FileState FindByFilePath(string absolutePath)
181 if (string.IsNullOrWhiteSpace(absolutePath))
182 throw new ArgumentNullException("absolutePath");
183 Contract.EndContractBlock();
189 return Queryable.FirstOrDefault(s => s.FilePath == absolutePath);
193 Log.Error(ex.ToString());
200 /* public static void DeleteByFilePath(string absolutePath)
202 if (string.IsNullOrWhiteSpace(absolutePath))
203 throw new ArgumentNullException("absolutePath");
204 Contract.EndContractBlock();
206 ExecuteWithRetry((session, instance) =>
208 const string hqlDelete = "delete FileState where FilePath = :path";
209 var deletedEntities = session.CreateQuery(hqlDelete)
210 .SetString("path", absolutePath)
212 return deletedEntities;
217 public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
219 if (string.IsNullOrWhiteSpace(absolutePath))
220 throw new ArgumentNullException("absolutePath");
221 Contract.EndContractBlock();
223 ExecuteWithRetry((session, instance) =>
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)
230 if (updatedEntities == 0)
232 var newState = new FileState
234 FilePath = absolutePath,
236 FileStatus = newStatus,
237 IsFolder=Directory.Exists(absolutePath)
239 newState.CreateAndFlush();
246 /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
248 if (string.IsNullOrWhiteSpace(absolutePath))
249 throw new ArgumentNullException("absolutePath");
250 Contract.EndContractBlock();
252 ExecuteWithRetry((session, instance) =>
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)
260 if (updatedEntities == 0)
262 var newState = new FileState
264 FilePath = absolutePath,
266 OverlayStatus = newStatus,
268 IsFolder=Directory.Exists(absolutePath)
270 newState.CreateAndFlush();
277 public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string etag)
279 if (string.IsNullOrWhiteSpace(absolutePath))
280 throw new ArgumentNullException("absolutePath");
281 Contract.EndContractBlock();
283 ExecuteWithRetry((session, instance) =>
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)
291 if (updatedEntities == 0)
293 var newState = new FileState
295 FilePath = absolutePath,
297 OverlayStatus = newStatus,
298 ETag = etag??String.Empty,
299 IsFolder=Directory.Exists(absolutePath)
301 newState.CreateAndFlush();
309 public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
311 if (string.IsNullOrWhiteSpace(absolutePath))
312 throw new ArgumentNullException("absolutePath");
313 Contract.EndContractBlock();
315 ExecuteWithRetry((session, instance) =>
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)
324 return updatedEntities;
331 public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
333 if (string.IsNullOrWhiteSpace(absolutePath))
334 throw new ArgumentNullException("absolutePath");
335 Contract.EndContractBlock();
337 ExecuteWithRetry((session, instance) =>
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)
345 return updatedEntities;
351 public static void RenameState(string oldPath, string newPath)
353 if (string.IsNullOrWhiteSpace(oldPath))
354 throw new ArgumentNullException("oldPath");
355 Contract.EndContractBlock();
357 ExecuteWithRetry((session, instance) =>
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)
365 return updatedEntities;
370 /* public static void UpdateStatus(Guid id, FileStatus fileStatus)
373 ExecuteWithRetry((session, instance) =>
375 const string hqlUpdate =
376 "update FileState set FileStatus= :fileStatus where Id = :id ";
377 var updatedEntities = session.CreateQuery(hqlUpdate)
379 .SetEnum("fileStatus", fileStatus)
381 return updatedEntities;
385 public static void UpdateChecksum(string absolutePath, string etag, string checksum)
387 if (string.IsNullOrWhiteSpace(absolutePath))
388 throw new ArgumentNullException("absolutePath");
389 Contract.EndContractBlock();
391 ExecuteWithRetry((session, instance) =>
393 const string hqlUpdate = "update FileState set Checksum= :checksum,ETag=:etag where FilePath = :path ";
394 var updatedEntities = session.CreateQuery(hqlUpdate)
395 .SetString("path", absolutePath)
396 .SetString("checksum", checksum)
397 .SetString("etag", etag)
399 return updatedEntities;
404 public static void UpdateLastMD5(FileInfo file, string md5)
407 throw new ArgumentNullException("file");
408 Contract.EndContractBlock();
410 ExecuteWithRetry((session, instance) =>
412 const string hqlUpdate = "update FileState set LastMD5=:md5, LastWriteDate=:date,LastLength=:length where FilePath = :path ";
413 var fullName = file.WithProperCapitalization().FullName;
415 var updatedEntities = session.CreateQuery(hqlUpdate)
416 .SetDateTime("date", file.LastWriteTime)
417 .SetInt64("length", file.Length)
418 .SetString("md5", md5)
419 .SetString("path", fullName)
421 if (updatedEntities == 0)
423 var newState = new FileState
427 OverlayStatus = FileOverlayStatus.Normal,
428 FileStatus=FileStatus.Unchanged,
430 LastLength=file.Length,
431 LastWriteDate=file.LastWriteTime,
432 LastMD5 = md5 ?? String.Empty,
435 newState.CreateAndFlush();
437 return updatedEntities;
442 public static void ChangeRootPath(string oldPath, string newPath)
444 if (String.IsNullOrWhiteSpace(oldPath))
445 throw new ArgumentNullException("oldPath");
446 if (!Path.IsPathRooted(oldPath))
447 throw new ArgumentException("oldPath must be an absolute path", "oldPath");
448 if (string.IsNullOrWhiteSpace(newPath))
449 throw new ArgumentNullException("newPath");
450 if (!Path.IsPathRooted(newPath))
451 throw new ArgumentException("newPath must be an absolute path", "newPath");
452 Contract.EndContractBlock();
454 //Ensure the paths end with the same character
455 if (!oldPath.EndsWith("\\"))
456 oldPath = oldPath + "\\";
457 if (!newPath.EndsWith("\\"))
458 newPath = newPath + "\\";
460 ExecuteWithRetry((session, instance) =>
462 const string hqlUpdate =
463 "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
464 var renames = session.CreateQuery(hqlUpdate)
465 .SetString("oldPath", oldPath)
466 .SetString("newPath", newPath)
472 public static FileState CreateFor(FileSystemInfo info,IStatusNotification notification)
475 throw new ArgumentNullException("info");
476 Contract.EndContractBlock();
478 if (info is DirectoryInfo)
481 FilePath = info.FullName,
482 OverlayStatus = FileOverlayStatus.Unversioned,
483 FileStatus = FileStatus.Created,
485 LastMD5=String.Empty,
490 var etag = ((FileInfo)info).ComputeShortHash(notification);
491 var fileState = new FileState
493 FilePath = info.FullName,
494 OverlayStatus = FileOverlayStatus.Unversioned,
495 FileStatus = FileStatus.Created,
497 LastMD5=String.Empty,
504 private static void ExecuteWithRetry(NHibernateDelegate call, object state)
510 using (new SessionScope())
512 Execute(call, state);
516 catch (ActiveRecordException )
525 /// Mark Unversioned all FileState rows from the database whose path
526 /// starts with one of the removed paths
528 /// <param name="removed"></param>
529 public static void UnversionPaths(List<string> removed)
533 if (removed.Count == 0)
536 //Create a disjunction (list of OR statements
537 var disjunction = new Disjunction();
538 foreach (var path in removed)
540 //with the restriction FileState.FilePath like '@path%'
541 disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)
542 .IsLike(path, MatchMode.Start));
545 //Generate a query from the disjunction
546 var query=QueryOver.Of<FileState>().Where(disjunction);
548 ExecuteWithRetry((session,instance)=>
550 using (var t=session.BeginTransaction())
552 var states = query.GetExecutableQueryOver(session).List();
553 foreach (var state in states)
555 state.FileStatus = FileStatus.Unversioned;
556 state.OverlayStatus = FileOverlayStatus.Unversioned;
567 [ActiveRecord("Tags")]
568 public class FileTag : ActiveRecordLinqBase<FileTag>
571 public int Id { get; set; }
574 public string Name { get; set; }
577 public string Value { get; set; }
579 [BelongsTo("FileStateId")]
580 public FileState FileState { get; set; }