X-Git-Url: https://code.grnet.gr/git/pithos-ms-client/blobdiff_plain/ab2f6f7915af9731514edb4e288e3e332defeebc..225694f964556a795da2faac950e3f5c12a16f4d:/trunk/Pithos.Core/FileState.cs diff --git a/trunk/Pithos.Core/FileState.cs b/trunk/Pithos.Core/FileState.cs index 54e44a3..3995cac 100644 --- a/trunk/Pithos.Core/FileState.cs +++ b/trunk/Pithos.Core/FileState.cs @@ -1,45 +1,83 @@ -// ----------------------------------------------------------------------- -// -// TODO: Update copyright text. -// -// ----------------------------------------------------------------------- - +#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.Engine; +using NHibernate.Criterion; using Pithos.Core.Agents; using Pithos.Interfaces; using Pithos.Network; +using log4net; namespace Pithos.Core { using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; + using System.Collections.Generic; /// /// TODO: Update summary. /// [ActiveRecord] - public class FileState:ActiveRecordLinqBase + public class FileState : ActiveRecordLinqBase { - private string _filePath; - private IList _tags=new List(); + 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 { return _filePath; } - set { _filePath = value.ToLower(); } - } + + //[Property(Unique = true, UniqueKey = "IX_FileState_ObjectID")] + [Property] + public string ObjectID { get; set; } + + [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")] + public string FilePath { get; set; } [Property] public FileOverlayStatus OverlayStatus { get; set; } @@ -48,12 +86,49 @@ namespace Pithos.Core public FileStatus FileStatus { get; set; } [Property] - public string Checksum { get; set; } + public string ConflictReason { 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 TopHash { get; set; } -*/ + 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; } @@ -71,44 +146,77 @@ namespace Pithos.Core [Property] public bool ShareWrite { get; set; } + [Property] + public bool IsFolder{ get; set; } - [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true,Inverse=true)] + [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)] public IList Tags { - get { return _tags; } - set { _tags=value;} + 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); } -// [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true)] -// public IList Hashes { get; set; } + public string GetRelativeUrl(AccountInfo accountInfo) + { + if (accountInfo==null) + throw new ArgumentNullException("accountInfo"); + Contract.EndContractBlock(); -// [Property] -// public byte[] HashmapHash { get; set; } - - public static FileState FindByFilePath(string absolutePath) + var fsi=GetFileSystemInfo(); + return fsi.AsRelativeUrlTo(accountInfo.AccountPath); + } + /*public static FileState FindByFilePath(string absolutePath) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); - return Queryable.FirstOrDefault(s => s.FilePath == absolutePath.ToLower()); - } + try + { + + - public static void DeleteByFilePath(string absolutePath) + 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)) + if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); - - FileState.Execute((session, instance) => - { - const string hqlDelete = "delete FileState where FilePath = :path"; - var deletedEntities = session.CreateQuery(hqlDelete) - .SetString("path", absolutePath.ToLower()) - .ExecuteUpdate(); - return null; - },null); - - } + + 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) { @@ -116,135 +224,188 @@ namespace Pithos.Core throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); - Execute((session, instance) => - { - const string hqlUpdate = "update FileState set FileStatus= :status where FilePath = :path "; - var updatedEntities = session.CreateQuery(hqlUpdate) - .SetString("path", absolutePath.ToLower()) - .SetEnum("status", newStatus) - .ExecuteUpdate(); - if (updatedEntities == 0) - { - var newState = new FileState { FilePath = absolutePath, Id = Guid.NewGuid(), FileStatus = newStatus }; - newState.CreateAndFlush(); - } - return null; - }, null); + 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) + /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); - Execute((session, instance) => - { - const string hqlUpdate = "update FileState set OverlayStatus= :status where FilePath = :path "; - var updatedEntities = session.CreateQuery(hqlUpdate) - .SetString("path", absolutePath.ToLower()) - .SetEnum("status", newStatus) - .ExecuteUpdate(); - if (updatedEntities == 0) - { - var newState = new FileState { FilePath = absolutePath, Id = Guid.NewGuid(), OverlayStatus = newStatus }; - newState.CreateAndFlush(); - } - return null; - }, null); + 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(); - Execute((session, instance) => - { - const string hqlUpdate = "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path "; - var updatedEntities = session.CreateQuery(hqlUpdate) - .SetString("path", absolutePath.ToLower()) - .SetEnum("fileStatus", fileStatus) - .SetEnum("overlayStatus", overlayStatus) - .ExecuteUpdate(); - return null; - }, null); + 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(); - Execute((session, instance) => - { - const string hqlUpdate = "update FileState set FileStatus= :fileStatus where FilePath = :path "; - var updatedEntities = session.CreateQuery(hqlUpdate) - .SetString("path", absolutePath.ToLower()) - .SetEnum("fileStatus", fileStatus) - .ExecuteUpdate(); - return updatedEntities; - }, null); + 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(); - Execute((session, instance) => - { - const string hqlUpdate = "update FileState set FilePath= :newPath where FilePath = :oldPath "; - var updatedEntities = session.CreateQuery(hqlUpdate) - .SetString("oldPath", oldPath.ToLower()) - .SetString("newPath", newPath.ToLower()) - .ExecuteUpdate(); - return updatedEntities; - }, null); + 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) + /* public static void UpdateStatus(Guid id, FileStatus fileStatus) { - Contract.EndContractBlock(); - Execute((session, instance) => + ExecuteWithRetry((session, instance) => { - const string hqlUpdate = "update FileState set FileStatus= :fileStatus where Id = :id "; + const string hqlUpdate = + "update FileState set FileStatus= :fileStatus where Id = :id "; var updatedEntities = session.CreateQuery(hqlUpdate) - .SetGuid("id", id) - .SetEnum("fileStatus", fileStatus) - .ExecuteUpdate(); - return null; + .SetGuid("id", id) + .SetEnum("fileStatus", fileStatus) + .ExecuteUpdate(); + return updatedEntities; }, null); + }*/ - } - - public static void UpdateChecksum(string absolutePath, string checksum) + public static void UpdateChecksum(string absolutePath, string shortHash, string checksum) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); Contract.EndContractBlock(); - Execute((session, instance) => - { - const string hqlUpdate = "update FileState set Checksum= :checksum where FilePath = :path "; - var updatedEntities = session.CreateQuery(hqlUpdate) - .SetString("path", absolutePath.ToLower()) - .SetString("checksum", checksum) - .ExecuteUpdate(); - return null; - }, null); + 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) + public static void ChangeRootPath(string oldPath, string newPath) { if (String.IsNullOrWhiteSpace(oldPath)) throw new ArgumentNullException("oldPath"); @@ -262,68 +423,109 @@ namespace Pithos.Core if (!newPath.EndsWith("\\")) newPath = newPath + "\\"; - using (new TransactionScope()) - { - Execute((session, instance) => + ExecuteWithRetry((session, instance) => { const string hqlUpdate = "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' "; - var result=session.CreateQuery(hqlUpdate) - .SetString("oldPath", oldPath.ToLower()) - .SetString("newPath", newPath.ToLower()) + var renames = session.CreateQuery(hqlUpdate) + .SetString("oldPath", oldPath) + .SetString("newPath", newPath) .ExecuteUpdate(); - return null; + return renames; }, null); - } } - public static Task CreateForAsync(string filePath,int blockSize,string algorithm) + public static FileState CreateFor(FileSystemInfo info,IStatusNotification notification) { - if (blockSize <= 0) - throw new ArgumentOutOfRangeException("blockSize"); - if (String.IsNullOrWhiteSpace(algorithm)) - throw new ArgumentNullException("algorithm"); + if(info==null) + throw new ArgumentNullException("info"); 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(notification); var fileState = new FileState { - FilePath = filePath, - OverlayStatus = FileOverlayStatus.Unversioned, - FileStatus = FileStatus.Created, - Id=Guid.NewGuid() + FilePath = info.FullName, + OverlayStatus = FileOverlayStatus.Unversioned, + FileStatus = FileStatus.Created, + ShortHash=shortHash, + Id = Guid.NewGuid() }; + return fileState; + } - return fileState.UpdateHashesAsync(blockSize,algorithm); + 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; + } } - public Task UpdateHashesAsync(int blockSize,string algorithm) + /// + /// Mark Unversioned all FileState rows from the database whose path + /// starts with one of the removed paths + /// + /// + public static void UnversionPaths(List removed) { - if (blockSize<=0) - throw new ArgumentOutOfRangeException("blockSize"); - if (String.IsNullOrWhiteSpace(algorithm)) - throw new ArgumentNullException("algorithm"); - Contract.EndContractBlock(); - - //Skip updating the hash for folders - if (Directory.Exists(FilePath)) - return Task.Factory.FromResult(this); - - var results = Task.Factory.StartNew(() => + 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) { - var info = new FileInfo(FilePath); - return info.CalculateHash(blockSize, algorithm); - }); + //with the restriction FileState.FilePath like '@path%' + disjunction.Add(Restrictions.On(s => s.FilePath) + .IsLike(path, MatchMode.Start)); + } - var state=results.Then(hash => - { - Checksum = hash; - return Task.Factory.FromResult(this); - }); - - return state; + //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")] @@ -342,23 +544,5 @@ namespace Pithos.Core public FileState FileState { get; set; } } - - /* [ActiveRecord("hashes")] - public class FileHash : ActiveRecordLinqBase - { - [PrimaryKey] - public int Id { get; set; } - - [Property] - public int Order { get; set; } - - [Property] - public byte[] Value { get; set; } - - [BelongsTo("FileStateID")] - public FileState FileState { get; set; } - - }*/ - - + }