#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_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; }
[Property]
public FileStatus FileStatus { get; set; }
[Property]
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 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)
{
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;
}
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; }
}
}