X-Git-Url: https://code.grnet.gr/git/pithos-ms-client/blobdiff_plain/bfc13ed8347269a909b77988f9e039c1d9017837..d78d765c9a999500ba4faa22fe21384481e2ec60:/trunk/Pithos.Core/FileState.cs?ds=sidebyside diff --git a/trunk/Pithos.Core/FileState.cs b/trunk/Pithos.Core/FileState.cs index b6911c3..2c3869a 100644 --- a/trunk/Pithos.Core/FileState.cs +++ b/trunk/Pithos.Core/FileState.cs @@ -1,43 +1,79 @@ -// ----------------------------------------------------------------------- -// -// 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 NHibernate.Engine; +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; - 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_FilePath")] + public string FilePath { get; set; } [Property] public FileOverlayStatus OverlayStatus { get; set; } @@ -45,80 +81,444 @@ namespace Pithos.Core [Property] public FileStatus FileStatus { get; set; } - [Property] - public string Checksum { 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; } [Property] public DateTime? VersionTimeStamp { get; set; } - - [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true,Inverse=true)] + + [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;} + 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) { - return Queryable.FirstOrDefault(s => s.FilePath == absolutePath.ToLower()); + 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 Task CreateForAsync(string filePath,int blockSize,string algorithm) + /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus) { - if (blockSize <= 0) - throw new ArgumentOutOfRangeException("blockSize"); - if (String.IsNullOrWhiteSpace(algorithm)) - throw new ArgumentNullException("algorithm"); + 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); - var fileState = new FileState - { - FilePath = filePath, - OverlayStatus = FileOverlayStatus.Unversioned, - FileStatus = FileStatus.Created, - Id=Guid.NewGuid() - }; + } +*/ + 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); - return fileState.UpdateHashesAsync(blockSize,algorithm); } - public Task UpdateHashesAsync(int blockSize,string algorithm) + public static void ChangeRootPath(string oldPath, string newPath) { - if (blockSize<=0) - throw new ArgumentOutOfRangeException("blockSize"); - if (String.IsNullOrWhiteSpace(algorithm)) - throw new ArgumentNullException("algorithm"); + 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(); - //Skip updating the hash for folders - if (Directory.Exists(FilePath)) - return Task.Factory.StartNew(() => this); + //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) + { + 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(); + var fileState = new FileState + { + FilePath = info.FullName, + OverlayStatus = FileOverlayStatus.Unversioned, + FileStatus = FileStatus.Created, + ShortHash=shortHash, + Id = Guid.NewGuid() + }; + return fileState; + } + - return Task.Factory.StartNew(() => - { - Checksum = Signature.CalculateMD5(FilePath); - TopHash = - Signature.CalculateTreeHash(FilePath, blockSize, algorithm) - .TopHash.ToHashString(); - }) - .ContinueWith( - t => this); + 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")] @@ -137,23 +537,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; } - - }*/ - - + }