#region /* ----------------------------------------------------------------------- * * * Copyright 2011-2012 GRNET S.A. All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * 1. Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and * documentation are those of the authors and should not be * interpreted as representing official policies, either expressed * or implied, of GRNET S.A. * * ----------------------------------------------------------------------- */ #endregion using System.Diagnostics.Contracts; using System.IO; using System.Reflection; using System.Threading.Tasks; using Castle.ActiveRecord; using Castle.ActiveRecord.Framework; using Castle.ActiveRecord.Queries; using NHibernate.Criterion; using Pithos.Core.Agents; using Pithos.Interfaces; using Pithos.Network; using log4net; namespace Pithos.Core { using System; using System.Collections.Generic; /// /// TODO: Update summary. /// [ActiveRecord] public class FileState : ActiveRecordLinqBase { private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private IList _tags = new List(); [PrimaryKey(PrimaryKeyType.Guid)] public Guid Id { get; set; } [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")] public string FilePath { get; set; } [Property] public FileOverlayStatus OverlayStatus { get; set; } [Property] public FileStatus FileStatus { get; set; } private string _checksum; /// /// The tophash value of the file, calculated by using a Merkle hash with the SHA256 algorithm /// /// /// The SHA256 algorithm is substantially more expenive than other algorithms. /// Recalculating the Checksum should be avoided whenever possible. /// [Property] public string Checksum { get { return _checksum; } set { _checksum = value; } } private string _shortHash; /// /// An easy to calcualte hash over the entire file, used to detect file changes /// /// The algorithm used to calculate this hash should be cheap [Property(NotNull=true,Default="")] public string ShortHash { get { return _shortHash; } set { _shortHash = value; } } [Property] public long? Version { get; set; } [Property] public DateTime? VersionTimeStamp { get; set; } [Property] public bool IsShared { get; set; } [Property] public string SharedBy { get; set; } [Property] public bool ShareWrite { get; set; } [Property] public bool IsFolder{ get; set; } [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)] public IList Tags { get { return _tags; } set { _tags = value; } } [Property] public DateTime Modified { get; set; } public FileSystemInfo GetFileSystemInfo() { if (String.IsNullOrWhiteSpace(FilePath)) throw new InvalidOperationException(); Contract.EndContractBlock(); return Directory.Exists(FilePath) ? (FileSystemInfo)new DirectoryInfo(FilePath) : new FileInfo(FilePath); } public string GetRelativeUrl(AccountInfo accountInfo) { if (accountInfo==null) throw new ArgumentNullException("accountInfo"); Contract.EndContractBlock(); var fsi=GetFileSystemInfo(); return fsi.AsRelativeUrlTo(accountInfo.AccountPath); } /*public static FileState FindByFilePath(string absolutePath) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); try { return Queryable.FirstOrDefault(s => s.FilePath == absolutePath); } catch (Exception ex) { Log.Error(ex.ToString()); throw; } }*/ /* public static void DeleteByFilePath(string absolutePath) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); ExecuteWithRetry((session, instance) => { const string hqlDelete = "delete FileState where FilePath = :path"; var deletedEntities = session.CreateQuery(hqlDelete) .SetString("path", absolutePath) .ExecuteUpdate(); return deletedEntities; }, null); }*/ public static void StoreFileStatus(string absolutePath, FileStatus newStatus) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set FileStatus= :status where FilePath = :path "; var updatedEntities = session.CreateQuery(hqlUpdate) .SetString("path", absolutePath) .SetEnum("status", newStatus) .ExecuteUpdate(); if (updatedEntities == 0) { var newState = new FileState { FilePath = absolutePath, Id = Guid.NewGuid(), FileStatus = newStatus, IsFolder=Directory.Exists(absolutePath) }; newState.CreateAndFlush(); } return null; }, null); } /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set OverlayStatus= :status where FilePath = :path "; var updatedEntities = session.CreateQuery(hqlUpdate) .SetString("path", absolutePath) .SetEnum("status", newStatus) .ExecuteUpdate(); if (updatedEntities == 0) { var newState = new FileState { FilePath = absolutePath, Id = Guid.NewGuid(), OverlayStatus = newStatus, ShortHash = String.Empty, IsFolder=Directory.Exists(absolutePath) }; newState.CreateAndFlush(); } return null; }, null); } */ public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string shortHash) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set OverlayStatus= :status where FilePath = :path "; var updatedEntities = session.CreateQuery(hqlUpdate) .SetString("path", absolutePath) .SetEnum("status", newStatus) .ExecuteUpdate(); if (updatedEntities == 0) { var newState = new FileState { FilePath = absolutePath, Id = Guid.NewGuid(), OverlayStatus = newStatus, ShortHash = shortHash??String.Empty, IsFolder=Directory.Exists(absolutePath) }; newState.CreateAndFlush(); } return null; }, null); } /* public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path "; var updatedEntities = session.CreateQuery(hqlUpdate) .SetString("path", absolutePath) .SetEnum("fileStatus", fileStatus) .SetEnum("overlayStatus", overlayStatus) .ExecuteUpdate(); return updatedEntities; }, null); } */ /* public static void UpdateStatus(string absolutePath, FileStatus fileStatus) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set FileStatus= :fileStatus where FilePath = :path "; var updatedEntities = session.CreateQuery(hqlUpdate) .SetString("path", absolutePath) .SetEnum("fileStatus", fileStatus) .ExecuteUpdate(); return updatedEntities; }, null); } */ public static void RenameState(string oldPath, string newPath) { if (string.IsNullOrWhiteSpace(oldPath)) throw new ArgumentNullException("oldPath"); Contract.EndContractBlock(); ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set FilePath= :newPath where FilePath = :oldPath "; var updatedEntities = session.CreateQuery(hqlUpdate) .SetString("oldPath", oldPath) .SetString("newPath", newPath) .ExecuteUpdate(); return updatedEntities; }, null); } /* public static void UpdateStatus(Guid id, FileStatus fileStatus) { ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set FileStatus= :fileStatus where Id = :id "; var updatedEntities = session.CreateQuery(hqlUpdate) .SetGuid("id", id) .SetEnum("fileStatus", fileStatus) .ExecuteUpdate(); return updatedEntities; }, null); }*/ public static void UpdateChecksum(string absolutePath, string shortHash, string checksum) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set Checksum= :checksum,ShortHash=:shortHash where FilePath = :path "; var updatedEntities = session.CreateQuery(hqlUpdate) .SetString("path", absolutePath) .SetString("checksum", checksum) .SetString("shortHash", shortHash) .ExecuteUpdate(); return updatedEntities; }, null); } public static void ChangeRootPath(string oldPath, string newPath) { if (String.IsNullOrWhiteSpace(oldPath)) throw new ArgumentNullException("oldPath"); if (!Path.IsPathRooted(oldPath)) throw new ArgumentException("oldPath must be an absolute path", "oldPath"); if (string.IsNullOrWhiteSpace(newPath)) throw new ArgumentNullException("newPath"); if (!Path.IsPathRooted(newPath)) throw new ArgumentException("newPath must be an absolute path", "newPath"); Contract.EndContractBlock(); //Ensure the paths end with the same character if (!oldPath.EndsWith("\\")) oldPath = oldPath + "\\"; if (!newPath.EndsWith("\\")) newPath = newPath + "\\"; ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' "; var renames = session.CreateQuery(hqlUpdate) .SetString("oldPath", oldPath) .SetString("newPath", newPath) .ExecuteUpdate(); return renames; }, null); } public static FileState CreateFor(FileSystemInfo info) { Contract.Requires(info!=null); Contract.EndContractBlock(); if (info is DirectoryInfo) return new FileState { FilePath = info.FullName, OverlayStatus = FileOverlayStatus.Unversioned, FileStatus = FileStatus.Created, ShortHash=String.Empty, Id = Guid.NewGuid() }; var shortHash = ((FileInfo)info).ComputeShortHash(); var fileState = new FileState { FilePath = info.FullName, OverlayStatus = FileOverlayStatus.Unversioned, FileStatus = FileStatus.Created, ShortHash=shortHash, Id = Guid.NewGuid() }; return fileState; } private static void ExecuteWithRetry(NHibernateDelegate call, object state) { int retries = 3; while (retries > 0) try { using (new SessionScope()) { Execute(call, state); return; } } catch (ActiveRecordException ) { retries--; if (retries <= 0) throw; } } /// /// Mark Unversioned all FileState rows from the database whose path /// starts with one of the removed paths /// /// public static void UnversionPaths(List removed) { if (removed == null) return; if (removed.Count == 0) return; //Create a disjunction (list of OR statements var disjunction = new Disjunction(); foreach (var path in removed) { //with the restriction FileState.FilePath like '@path%' disjunction.Add(Restrictions.On(s => s.FilePath) .IsLike(path, MatchMode.Start)); } //Generate a query from the disjunction var query=QueryOver.Of().Where(disjunction); ExecuteWithRetry((session,instance)=> { using (var t=session.BeginTransaction()) { var states = query.GetExecutableQueryOver(session).List(); foreach (var state in states) { state.FileStatus = FileStatus.Unversioned; state.OverlayStatus = FileOverlayStatus.Unversioned; state.Update(); } t.Commit(); } return null; },null); } } [ActiveRecord("Tags")] public class FileTag : ActiveRecordLinqBase { [PrimaryKey] public int Id { get; set; } [Property] public string Name { get; set; } [Property] public string Value { get; set; } [BelongsTo("FileStateId")] public FileState FileState { get; set; } } }