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")]
76 public string ObjectID { get; set; }
78 [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")]
79 public string FilePath { get; set; }
82 public FileOverlayStatus OverlayStatus { get; set; }
85 public FileStatus FileStatus { get; set; }
88 public string ConflictReason { get; set; }
90 private string _checksum;
93 /// The tophash value of the file, calculated by using a Merkle hash with the SHA256 algorithm
96 /// The SHA256 algorithm is substantially more expenive than other algorithms.
97 /// Recalculating the Checksum should be avoided whenever possible.
100 public string Checksum
112 private string _shortHash;
115 /// An easy to calcualte hash over the entire file, used to detect file changes
117 /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
118 [Property(NotNull=true,Default="")]
119 public string ShortHash
133 public long? Version { get; set; }
136 public DateTime? VersionTimeStamp { get; set; }
140 public bool IsShared { get; set; }
143 public string SharedBy { get; set; }
146 public bool ShareWrite { get; set; }
149 public bool IsFolder{ get; set; }
151 [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)]
152 public IList<FileTag> Tags
154 get { return _tags; }
155 set { _tags = value; }
159 public DateTime Modified { get; set; }
162 public FileSystemInfo GetFileSystemInfo()
164 if (String.IsNullOrWhiteSpace(FilePath))
165 throw new InvalidOperationException();
166 Contract.EndContractBlock();
168 return Directory.Exists(FilePath) ?
169 (FileSystemInfo)new DirectoryInfo(FilePath)
170 : new FileInfo(FilePath);
173 public string GetRelativeUrl(AccountInfo accountInfo)
175 if (accountInfo==null)
176 throw new ArgumentNullException("accountInfo");
177 Contract.EndContractBlock();
179 var fsi=GetFileSystemInfo();
180 return fsi.AsRelativeUrlTo(accountInfo.AccountPath);
182 /*public static FileState FindByFilePath(string absolutePath)
184 if (string.IsNullOrWhiteSpace(absolutePath))
185 throw new ArgumentNullException("absolutePath");
186 Contract.EndContractBlock();
192 return Queryable.FirstOrDefault(s => s.FilePath == absolutePath);
196 Log.Error(ex.ToString());
203 /* public static void DeleteByFilePath(string absolutePath)
205 if (string.IsNullOrWhiteSpace(absolutePath))
206 throw new ArgumentNullException("absolutePath");
207 Contract.EndContractBlock();
209 ExecuteWithRetry((session, instance) =>
211 const string hqlDelete = "delete FileState where FilePath = :path";
212 var deletedEntities = session.CreateQuery(hqlDelete)
213 .SetString("path", absolutePath)
215 return deletedEntities;
220 public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
222 if (string.IsNullOrWhiteSpace(absolutePath))
223 throw new ArgumentNullException("absolutePath");
224 Contract.EndContractBlock();
226 ExecuteWithRetry((session, instance) =>
228 const string hqlUpdate = "update FileState set FileStatus= :status where FilePath = :path ";
229 var updatedEntities = session.CreateQuery(hqlUpdate)
230 .SetString("path", absolutePath)
231 .SetEnum("status", newStatus)
233 if (updatedEntities == 0)
235 var newState = new FileState
237 FilePath = absolutePath,
239 FileStatus = newStatus,
240 IsFolder=Directory.Exists(absolutePath)
242 newState.CreateAndFlush();
249 /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
251 if (string.IsNullOrWhiteSpace(absolutePath))
252 throw new ArgumentNullException("absolutePath");
253 Contract.EndContractBlock();
255 ExecuteWithRetry((session, instance) =>
257 const string hqlUpdate =
258 "update FileState set OverlayStatus= :status where FilePath = :path ";
259 var updatedEntities = session.CreateQuery(hqlUpdate)
260 .SetString("path", absolutePath)
261 .SetEnum("status", newStatus)
263 if (updatedEntities == 0)
265 var newState = new FileState
267 FilePath = absolutePath,
269 OverlayStatus = newStatus,
270 ShortHash = String.Empty,
271 IsFolder=Directory.Exists(absolutePath)
273 newState.CreateAndFlush();
280 public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string shortHash)
282 if (string.IsNullOrWhiteSpace(absolutePath))
283 throw new ArgumentNullException("absolutePath");
284 Contract.EndContractBlock();
286 ExecuteWithRetry((session, instance) =>
288 const string hqlUpdate =
289 "update FileState set OverlayStatus= :status where FilePath = :path ";
290 var updatedEntities = session.CreateQuery(hqlUpdate)
291 .SetString("path", absolutePath)
292 .SetEnum("status", newStatus)
294 if (updatedEntities == 0)
296 var newState = new FileState
298 FilePath = absolutePath,
300 OverlayStatus = newStatus,
301 ShortHash = shortHash??String.Empty,
302 IsFolder=Directory.Exists(absolutePath)
304 newState.CreateAndFlush();
312 public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
314 if (string.IsNullOrWhiteSpace(absolutePath))
315 throw new ArgumentNullException("absolutePath");
316 Contract.EndContractBlock();
318 ExecuteWithRetry((session, instance) =>
320 const string hqlUpdate =
321 "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path ";
322 var updatedEntities = session.CreateQuery(hqlUpdate)
323 .SetString("path", absolutePath)
324 .SetEnum("fileStatus", fileStatus)
325 .SetEnum("overlayStatus", overlayStatus)
327 return updatedEntities;
334 public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
336 if (string.IsNullOrWhiteSpace(absolutePath))
337 throw new ArgumentNullException("absolutePath");
338 Contract.EndContractBlock();
340 ExecuteWithRetry((session, instance) =>
342 const string hqlUpdate =
343 "update FileState set FileStatus= :fileStatus where FilePath = :path ";
344 var updatedEntities = session.CreateQuery(hqlUpdate)
345 .SetString("path", absolutePath)
346 .SetEnum("fileStatus", fileStatus)
348 return updatedEntities;
354 public static void RenameState(string oldPath, string newPath)
356 if (string.IsNullOrWhiteSpace(oldPath))
357 throw new ArgumentNullException("oldPath");
358 Contract.EndContractBlock();
360 ExecuteWithRetry((session, instance) =>
362 const string hqlUpdate =
363 "update FileState set FilePath= :newPath where FilePath = :oldPath ";
364 var updatedEntities = session.CreateQuery(hqlUpdate)
365 .SetString("oldPath", oldPath)
366 .SetString("newPath", newPath)
368 return updatedEntities;
373 /* public static void UpdateStatus(Guid id, FileStatus fileStatus)
376 ExecuteWithRetry((session, instance) =>
378 const string hqlUpdate =
379 "update FileState set FileStatus= :fileStatus where Id = :id ";
380 var updatedEntities = session.CreateQuery(hqlUpdate)
382 .SetEnum("fileStatus", fileStatus)
384 return updatedEntities;
388 public static void UpdateChecksum(string absolutePath, string shortHash, string checksum)
390 if (string.IsNullOrWhiteSpace(absolutePath))
391 throw new ArgumentNullException("absolutePath");
392 Contract.EndContractBlock();
394 ExecuteWithRetry((session, instance) =>
396 const string hqlUpdate = "update FileState set Checksum= :checksum,ShortHash=:shortHash where FilePath = :path ";
397 var updatedEntities = session.CreateQuery(hqlUpdate)
398 .SetString("path", absolutePath)
399 .SetString("checksum", checksum)
400 .SetString("shortHash", shortHash)
402 return updatedEntities;
407 public static void ChangeRootPath(string oldPath, string newPath)
409 if (String.IsNullOrWhiteSpace(oldPath))
410 throw new ArgumentNullException("oldPath");
411 if (!Path.IsPathRooted(oldPath))
412 throw new ArgumentException("oldPath must be an absolute path", "oldPath");
413 if (string.IsNullOrWhiteSpace(newPath))
414 throw new ArgumentNullException("newPath");
415 if (!Path.IsPathRooted(newPath))
416 throw new ArgumentException("newPath must be an absolute path", "newPath");
417 Contract.EndContractBlock();
419 //Ensure the paths end with the same character
420 if (!oldPath.EndsWith("\\"))
421 oldPath = oldPath + "\\";
422 if (!newPath.EndsWith("\\"))
423 newPath = newPath + "\\";
425 ExecuteWithRetry((session, instance) =>
427 const string hqlUpdate =
428 "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
429 var renames = session.CreateQuery(hqlUpdate)
430 .SetString("oldPath", oldPath)
431 .SetString("newPath", newPath)
437 public static FileState CreateFor(FileSystemInfo info)
440 throw new ArgumentNullException("info");
441 Contract.EndContractBlock();
443 if (info is DirectoryInfo)
446 FilePath = info.FullName,
447 OverlayStatus = FileOverlayStatus.Unversioned,
448 FileStatus = FileStatus.Created,
449 ShortHash=String.Empty,
454 var shortHash = ((FileInfo)info).ComputeShortHash();
455 var fileState = new FileState
457 FilePath = info.FullName,
458 OverlayStatus = FileOverlayStatus.Unversioned,
459 FileStatus = FileStatus.Created,
467 private static void ExecuteWithRetry(NHibernateDelegate call, object state)
473 using (new SessionScope())
475 Execute(call, state);
479 catch (ActiveRecordException )
488 /// Mark Unversioned all FileState rows from the database whose path
489 /// starts with one of the removed paths
491 /// <param name="removed"></param>
492 public static void UnversionPaths(List<string> removed)
496 if (removed.Count == 0)
499 //Create a disjunction (list of OR statements
500 var disjunction = new Disjunction();
501 foreach (var path in removed)
503 //with the restriction FileState.FilePath like '@path%'
504 disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)
505 .IsLike(path, MatchMode.Start));
508 //Generate a query from the disjunction
509 var query=QueryOver.Of<FileState>().Where(disjunction);
511 ExecuteWithRetry((session,instance)=>
513 using (var t=session.BeginTransaction())
515 var states = query.GetExecutableQueryOver(session).List();
516 foreach (var state in states)
518 state.FileStatus = FileStatus.Unversioned;
519 state.OverlayStatus = FileOverlayStatus.Unversioned;
530 [ActiveRecord("Tags")]
531 public class FileTag : ActiveRecordLinqBase<FileTag>
534 public int Id { get; set; }
537 public string Name { get; set; }
540 public string Value { get; set; }
542 [BelongsTo("FileStateId")]
543 public FileState FileState { get; set; }