-// -----------------------------------------------------------------------
-// <copyright file="FileState.cs" company="GRNet">
-// Copyright 2011 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.
-// </copyright>
-// -----------------------------------------------------------------------
-
+#region
+/* -----------------------------------------------------------------------
+ * <copyright file="FileState.cs" company="GRNet">
+ *
+ * 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.
+ * </copyright>
+ * -----------------------------------------------------------------------
+ */
+#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;
- using System.Linq;
+ using System.Collections.Generic;
/// <summary>
/// TODO: Update summary.
/// </summary>
[ActiveRecord]
- public class FileState:ActiveRecordLinqBase<FileState>
+ public class FileState : ActiveRecordLinqBase<FileState>
{
- private static readonly ILog Log = LogManager.GetLogger("FileState");
-
- private string _filePath;
- private IList<FileTag> _tags=new List<FileTag>();
+ private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+
+ private IList<FileTag> _tags = new List<FileTag>();
[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; }
public FileStatus FileStatus { get; set; }
[Property]
- public string Checksum { get; set; }
+ public string ConflictReason { get; set; }
+
+ private string _checksum;
+
+ /// <summary>
+ /// The tophash value of the file, calculated by using a Merkle hash with the SHA256 algorithm
+ /// </summary>
+ /// <remarks>
+ /// The SHA256 algorithm is substantially more expenive than other algorithms.
+ /// Recalculating the Checksum should be avoided whenever possible.
+ /// </remarks>
+ [Property]
+ public string Checksum
+ {
+ get
+ {
+ return _checksum;
+ }
+ set
+ {
+ _checksum = value;
+ }
+ }
+
+ private string _shortHash;
+
+ /// <summary>
+ /// An easy to calcualte hash over the entire file, used to detect file changes
+ /// </summary>
+ /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
+ [Property(NotNull=true,Default="")]
+ public string ShortHash
+ {
+ get
+ {
+ return _shortHash;
+ }
+ set
+ {
+ _shortHash = value;
+ }
+ }
[Property]
[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<FileTag> Tags
{
- get { return _tags; }
- set { _tags=value;}
+ get { return _tags; }
+ set { _tags = value; }
}
-
- public static FileState FindByFilePath(string absolutePath)
+ [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.ToLower());
+
+
+
+ return Queryable.FirstOrDefault(s => s.FilePath == absolutePath);
}
catch (Exception ex)
{
Log.Error(ex.ToString());
throw;
}
-
- }
- public static void DeleteByFilePath(string absolutePath)
+ }*/
+
+ /* public static void DeleteByFilePath(string absolutePath)
{
- if(string.IsNullOrWhiteSpace(absolutePath))
+ if (string.IsNullOrWhiteSpace(absolutePath))
throw new ArgumentNullException("absolutePath");
Contract.EndContractBlock();
-
- Execute((session, instance) =>
- {
- const string hqlDelete = "delete FileState where FilePath = :path";
- var deletedEntities = session.CreateQuery(hqlDelete)
- .SetString("path", absolutePath.ToLower())
- .ExecuteUpdate();
- return deletedEntities;
- },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)
{
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.ToLower(), 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 updatedEntities;
- }, 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();
+ .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 updatedEntities;
- }, 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");
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 renames=session.CreateQuery(hqlUpdate)
- .SetString("oldPath", oldPath.ToLower())
- .SetString("newPath", newPath.ToLower())
+ var renames = session.CreateQuery(hqlUpdate)
+ .SetString("oldPath", oldPath)
+ .SetString("newPath", newPath)
.ExecuteUpdate();
return renames;
}, null);
- }
}
- public static Task<FileState> 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.ToLower(),
- 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 async Task<FileState> UpdateHashesAsync(int blockSize,string algorithm)
+ /// <summary>
+ /// Mark Unversioned all FileState rows from the database whose path
+ /// starts with one of the removed paths
+ /// </summary>
+ /// <param name="removed"></param>
+ public static void UnversionPaths(List<string> 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 this;
-
- var hash = await TaskEx.Run(() =>
+ 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<FileState>(s => s.FilePath)
+ .IsLike(path, MatchMode.Start));
+ }
- Checksum = hash;
-
- return this;
+ //Generate a query from the disjunction
+ var query=QueryOver.Of<FileState>().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")]