From e81dd1f6fd93a8c094dff04db580385029fc8ee4 Mon Sep 17 00:00:00 2001 From: Panagiotis Kanavos Date: Mon, 9 Jan 2012 23:19:22 +0200 Subject: [PATCH] Convert ActiveRecord update code to direct ADO calls to reduce locks --- trunk/Pithos.Core/Agents/CloudTransferAction.cs | 9 +- trunk/Pithos.Core/Agents/NetworkAgent.cs | 18 +- trunk/Pithos.Core/Agents/StatusAgent.cs | 152 ++++++++++++- trunk/Pithos.Core/Agents/WorkflowAgent.cs | 52 +++-- trunk/Pithos.Core/DbConfig.xml | 2 +- trunk/Pithos.Core/FileState.cs | 273 +++++++++++++---------- trunk/Pithos.Interfaces/FileInfoExtensions.cs | 39 +++- 7 files changed, 381 insertions(+), 164 deletions(-) diff --git a/trunk/Pithos.Core/Agents/CloudTransferAction.cs b/trunk/Pithos.Core/Agents/CloudTransferAction.cs index 7bd5005..cc16292 100644 --- a/trunk/Pithos.Core/Agents/CloudTransferAction.cs +++ b/trunk/Pithos.Core/Agents/CloudTransferAction.cs @@ -108,19 +108,20 @@ namespace Pithos.Core.Agents public class CloudDeleteAction:CloudAction { public CloudDeleteAction(AccountInfo accountInfo,FileSystemInfo fileInfo, FileState fileState) - : this(accountInfo,new ObjectInfo(accountInfo.AccountPath,accountInfo.UserName,fileInfo),fileState) - { + : this(accountInfo,fileInfo,new ObjectInfo(accountInfo.AccountPath,accountInfo.UserName,fileInfo),fileState) + { } - public CloudDeleteAction(AccountInfo accountInfo, ObjectInfo cloudFile, FileState fileState) + public CloudDeleteAction(AccountInfo accountInfo, FileSystemInfo fileInfo,ObjectInfo cloudFile, FileState fileState) : base(accountInfo,CloudActionType.DeleteCloud) { CloudFile = cloudFile; + LocalFile = fileInfo; FileState = fileState; } public CloudDeleteAction(CloudAction action) - : this(action.AccountInfo,action.CloudFile,action.FileState) + : this(action.AccountInfo,action.LocalFile,action.CloudFile,action.FileState) {} [ContractInvariantMethod] diff --git a/trunk/Pithos.Core/Agents/NetworkAgent.cs b/trunk/Pithos.Core/Agents/NetworkAgent.cs index 940c9f4..8c4792c 100644 --- a/trunk/Pithos.Core/Agents/NetworkAgent.cs +++ b/trunk/Pithos.Core/Agents/NetworkAgent.cs @@ -45,6 +45,7 @@ using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; +using Castle.ActiveRecord; using Pithos.Interfaces; using Pithos.Network; using log4net; @@ -61,7 +62,7 @@ namespace Pithos.Core.Agents private Agent _deleteAgent; - [Import] + [System.ComponentModel.Composition.Import] public IStatusKeeper StatusKeeper { get; set; } public IStatusNotification StatusNotification { get; set; } @@ -352,7 +353,7 @@ namespace Pithos.Core.Agents Contract.EndContractBlock(); //If the action targets a local file, add a treehash calculation - if (cloudAction.LocalFile as FileInfo != null) + if (!(cloudAction is CloudDeleteAction) && cloudAction.LocalFile as FileInfo != null) { var accountInfo = cloudAction.AccountInfo; var localFile = (FileInfo) cloudAction.LocalFile; @@ -548,12 +549,15 @@ namespace Pithos.Core.Agents var localFile = fileAgent.GetFileSystemInfo(relativePath); if (objectInfo.Content_Type == @"application/directory" && localFile is DirectoryInfo) continue; - var state = FileState.FindByFilePath(localFile.FullName); - //Common files should be checked on a per-case basis to detect differences, which is newer + using (new SessionScope(FlushAction.Never)) + { + var state = FileState.FindByFilePath(localFile.FullName); + //Common files should be checked on a per-case basis to detect differences, which is newer - yield return new CloudAction(accountInfo,CloudActionType.MustSynch, - localFile, objectInfo, state, accountInfo.BlockSize, - accountInfo.BlockHash); + yield return new CloudAction(accountInfo, CloudActionType.MustSynch, + localFile, objectInfo, state, accountInfo.BlockSize, + accountInfo.BlockHash); + } } else { diff --git a/trunk/Pithos.Core/Agents/StatusAgent.cs b/trunk/Pithos.Core/Agents/StatusAgent.cs index de7af7b..b21a311 100644 --- a/trunk/Pithos.Core/Agents/StatusAgent.cs +++ b/trunk/Pithos.Core/Agents/StatusAgent.cs @@ -61,7 +61,7 @@ namespace Pithos.Core.Agents }, }; - var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3", pithosDbPath); + var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N", pithosDbPath); properties.Add("connection.connection_string", connectionString); var source = new InPlaceConfigurationSource(); @@ -140,7 +140,7 @@ namespace Pithos.Core.Agents var pairs = currentFiles.Union(deletedFiles); - Parallel.ForEach(pairs, pair => + foreach(var pair in pairs) { var fileState = pair.State; var file = pair.File; @@ -156,7 +156,7 @@ namespace Pithos.Core.Agents //This file was deleted while we were down. We should mark it as deleted //We have to go through UpdateStatus here because the state object we are using //was created by a different ORM session. - FileState.UpdateStatus(fileState.Id,FileStatus.Deleted); + _persistenceAgent.Post(()=> UpdateStatusDirect(fileState.Id, FileStatus.Deleted)); } else { @@ -165,14 +165,93 @@ namespace Pithos.Core.Agents //If the hashes don't match the file was changed if (fileState.Checksum != hashString) { - FileState.UpdateStatus(fileState.Id, FileStatus.Modified); + _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Modified)); } } - }); + }; } - + private int UpdateStatusDirect(Guid id, FileStatus status) + { + try + { + + var connectionString = GetConnectionString(); + using (var connection = new SQLiteConnection(connectionString)) + { + var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id ", + connection); + + command.Parameters.AddWithValue("fileStatus", status); + + command.Parameters.AddWithValue("id", id); + connection.Open(); + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + + } + + private int UpdateStatusDirect(string path, FileStatus status) + { + try + { + + var connectionString = GetConnectionString(); + using (var connection = new SQLiteConnection(connectionString)) + { + var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path ", + connection); + + command.Parameters.AddWithValue("fileStatus", status); + + command.Parameters.AddWithValue("path", path.ToLower()); + connection.Open(); + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + + } + + private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus) + { + try + { + + var connectionString = GetConnectionString(); + using (var connection = new SQLiteConnection(connectionString)) + { + var command = new SQLiteCommand("update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path ", + connection); + command.Parameters.AddWithValue("path", absolutePath.ToLower()); + command.Parameters.AddWithValue("fileStatus", fileStatus); + command.Parameters.AddWithValue("overlayStatus", overlayStatus); + connection.Open(); + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + + } + public string BlockHash { get; set; } @@ -220,7 +299,15 @@ namespace Pithos.Core.Agents try { - + var connectionString = GetConnectionString(); + using (var connection = new SQLiteConnection(connectionString)) + { + var command = new SQLiteCommand("select OverlayStatus from FileState where FilePath=:path", connection); + command.Parameters.AddWithValue("path", path.ToLower()); + connection.Open(); + var s = command.ExecuteScalar(); + return (FileOverlayStatus) Convert.ToInt32(s); + } var status = from state in FileState.Queryable where state.FilePath ==path.ToLower() select state.OverlayStatus; @@ -233,6 +320,12 @@ namespace Pithos.Core.Agents } } + private string GetConnectionString() + { + var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True;Default Isolation Level=ReadCommited", _pithosDataPath); + return connectionString; + } + public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus) { if (String.IsNullOrWhiteSpace(path)) @@ -270,7 +363,7 @@ namespace Pithos.Core.Agents Debug.Assert(!path.Contains(FolderConstants.CacheFolder)); Debug.Assert(!path.EndsWith(".ignore")); - _persistenceAgent.Post(() => FileState.UpdateStatus(path.ToLower(), fileStatus, overlayStatus)); + _persistenceAgent.Post(() => UpdateStatusDirect(path.ToLower(), fileStatus, overlayStatus)); } public void StoreInfo(string path,ObjectInfo objectInfo) @@ -320,8 +413,8 @@ namespace Pithos.Core.Agents if (!Path.IsPathRooted(path)) throw new ArgumentException("The path must be rooted", "path"); Contract.EndContractBlock(); - - _persistenceAgent.Post(() => FileState.UpdateStatus(path.ToLower(), status)); + + _persistenceAgent.Post(() => UpdateStatusDirect(path.ToLower(), status)); } public FileStatus GetFileStatus(string path) @@ -332,6 +425,16 @@ namespace Pithos.Core.Agents throw new ArgumentException("The path must be rooted", "path"); Contract.EndContractBlock(); + var connectionString = GetConnectionString(); + using (var connection = new SQLiteConnection(connectionString)) + { + var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path", connection); + command.Parameters.AddWithValue("path", path.ToLower()); + connection.Open(); + var s = command.ExecuteScalar(); + return (FileStatus)Convert.ToInt32(s); + } + var status = from r in FileState.Queryable where r.FilePath == path.ToLower() select r.FileStatus; @@ -345,8 +448,33 @@ namespace Pithos.Core.Agents if (!Path.IsPathRooted(path)) throw new ArgumentException("The path must be rooted", "path"); Contract.EndContractBlock(); - - _persistenceAgent.Post(() => FileState.DeleteByFilePath(path)); + + _persistenceAgent.Post(() => DeleteDirect(path)); + } + + private int DeleteDirect(string filePath) + { + try + { + + var connectionString = GetConnectionString(); + using (var connection = new SQLiteConnection(connectionString)) + { + var command = new SQLiteCommand("delete FileState where FilePath = :path", + connection); + + command.Parameters.AddWithValue("path", filePath.ToLower()); + connection.Open(); + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } public void UpdateFileChecksum(string path, string checksum) diff --git a/trunk/Pithos.Core/Agents/WorkflowAgent.cs b/trunk/Pithos.Core/Agents/WorkflowAgent.cs index 81a52f5..1112076 100644 --- a/trunk/Pithos.Core/Agents/WorkflowAgent.cs +++ b/trunk/Pithos.Core/Agents/WorkflowAgent.cs @@ -44,6 +44,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Castle.ActiveRecord; using Pithos.Interfaces; using Pithos.Network; using log4net; @@ -56,10 +57,10 @@ namespace Pithos.Core.Agents Agent _agent; public IStatusNotification StatusNotification { get; set; } - [Import] + [System.ComponentModel.Composition.Import] public IStatusKeeper StatusKeeper { get; set; } - [Import] + [System.ComponentModel.Composition.Import] public NetworkAgent NetworkAgent { get; set; } private static readonly ILog Log = LogManager.GetLogger("WorkflowAgent"); @@ -114,26 +115,35 @@ namespace Pithos.Core.Agents return CompletedTask.Default; } - var fileState = FileState.FindByFilePath(path); - - - switch (state.Status) + using (new SessionScope(FlushAction.Never)) { - case FileStatus.Created: - case FileStatus.Modified: - NetworkAgent.Post(new CloudUploadAction(accountInfo, info, fileState, accountInfo.BlockSize, - accountInfo.BlockHash)); - break; - case FileStatus.Deleted: - NetworkAgent.Post(new CloudDeleteAction(accountInfo, info, fileState)); - break; - case FileStatus.Renamed: - FileSystemInfo oldInfo = Directory.Exists(state.OldPath) ? (FileSystemInfo)new DirectoryInfo(state.OldPath) : new FileInfo(state.OldPath); - FileSystemInfo newInfo = Directory.Exists(state.Path) ? (FileSystemInfo)new DirectoryInfo(state.Path) : new FileInfo(state.Path); - NetworkAgent.Post(new CloudMoveAction(accountInfo, CloudActionType.RenameCloud, - oldInfo, - newInfo)); - break; + + var fileState = FileState.FindByFilePath(path); + + + switch (state.Status) + { + case FileStatus.Created: + case FileStatus.Modified: + NetworkAgent.Post(new CloudUploadAction(accountInfo, info, fileState, + accountInfo.BlockSize, + accountInfo.BlockHash)); + break; + case FileStatus.Deleted: + NetworkAgent.Post(new CloudDeleteAction(accountInfo, info, fileState)); + break; + case FileStatus.Renamed: + FileSystemInfo oldInfo = Directory.Exists(state.OldPath) + ? (FileSystemInfo) new DirectoryInfo(state.OldPath) + : new FileInfo(state.OldPath); + FileSystemInfo newInfo = Directory.Exists(state.Path) + ? (FileSystemInfo) new DirectoryInfo(state.Path) + : new FileInfo(state.Path); + NetworkAgent.Post(new CloudMoveAction(accountInfo, CloudActionType.RenameCloud, + oldInfo, + newInfo)); + break; + } } return CompletedTask.Default; diff --git a/trunk/Pithos.Core/DbConfig.xml b/trunk/Pithos.Core/DbConfig.xml index 586b9e7..c445f32 100644 --- a/trunk/Pithos.Core/DbConfig.xml +++ b/trunk/Pithos.Core/DbConfig.xml @@ -4,7 +4,7 @@ - + diff --git a/trunk/Pithos.Core/FileState.cs b/trunk/Pithos.Core/FileState.cs index df43910..e523cc7 100644 --- a/trunk/Pithos.Core/FileState.cs +++ b/trunk/Pithos.Core/FileState.cs @@ -35,6 +35,7 @@ // // ----------------------------------------------------------------------- +using System.Data.SQLite; using System.Diagnostics.Contracts; using System.IO; using System.Threading.Tasks; @@ -54,17 +55,17 @@ namespace Pithos.Core /// TODO: Update summary. /// [ActiveRecord] - public class FileState:ActiveRecordLinqBase + public class FileState : ActiveRecordLinqBase { private static readonly ILog Log = LogManager.GetLogger("FileState"); - + private string _filePath; - private IList _tags=new List(); + private IList _tags = new List(); [PrimaryKey(PrimaryKeyType.Guid)] public Guid Id { get; set; } - [Property(Unique=true,UniqueKey="IX_FileState_FilePath")] + [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")] public string FilePath { get { return _filePath; } @@ -98,14 +99,14 @@ namespace Pithos.Core public bool ShareWrite { 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; } } - + public static FileState FindByFilePath(string absolutePath) { if (string.IsNullOrWhiteSpace(absolutePath)) @@ -113,6 +114,9 @@ namespace Pithos.Core Contract.EndContractBlock(); try { + + + return Queryable.FirstOrDefault(s => s.FilePath == absolutePath.ToLower()); } catch (Exception ex) @@ -120,26 +124,26 @@ namespace Pithos.Core 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.ToLower()) + .ExecuteUpdate(); + return deletedEntities; + }, null); + + }*/ public static void StoreFileStatus(string absolutePath, FileStatus newStatus) { @@ -147,20 +151,25 @@ 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.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.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); } @@ -170,92 +179,105 @@ namespace Pithos.Core 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.ToLower()) + .SetEnum("status", newStatus) + .ExecuteUpdate(); + if (updatedEntities == 0) + { + var newState = new FileState + { + FilePath = absolutePath, + Id = Guid.NewGuid(), + OverlayStatus = newStatus + }; + 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.ToLower()) + .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.ToLower()) + .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.ToLower()) + .SetString("newPath", newPath.ToLower()) + .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) { @@ -263,19 +285,19 @@ namespace Pithos.Core 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 where FilePath = :path "; + var updatedEntities = session.CreateQuery(hqlUpdate) + .SetString("path", absolutePath.ToLower()) + .SetString("checksum", checksum) + .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"); @@ -293,22 +315,19 @@ 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 renames=session.CreateQuery(hqlUpdate) + var renames = session.CreateQuery(hqlUpdate) .SetString("oldPath", oldPath.ToLower()) .SetString("newPath", newPath.ToLower()) .ExecuteUpdate(); return renames; }, null); - } } - public static Task CreateForAsync(string filePath,int blockSize,string algorithm) + public static Task CreateForAsync(string filePath, int blockSize, string algorithm) { if (blockSize <= 0) throw new ArgumentOutOfRangeException("blockSize"); @@ -319,38 +338,62 @@ namespace Pithos.Core var fileState = new FileState { - FilePath = filePath.ToLower(), - OverlayStatus = FileOverlayStatus.Unversioned, + FilePath = filePath.ToLower(), + OverlayStatus = FileOverlayStatus.Unversioned, FileStatus = FileStatus.Created, - Id=Guid.NewGuid() + Id = Guid.NewGuid() }; - return fileState.UpdateHashesAsync(blockSize,algorithm); + return fileState.UpdateHashesAsync(blockSize, algorithm); } - public async Task UpdateHashesAsync(int blockSize,string algorithm) + public async Task UpdateHashesAsync(int blockSize, string algorithm) { - if (blockSize<=0) + 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(() => - { - var info = new FileInfo(FilePath); - return info.CalculateHash(blockSize, algorithm); - }); + { + var info = new FileInfo(FilePath); + return info.CalculateHash(blockSize, algorithm); + }); Checksum = hash; - + return this; } + + private static void ExecuteWithRetry(NHibernateDelegate call, object state) + { + int retries = 3; + while (retries > 0) + try + { + using (new SessionScope()) + { + Execute(call, state); + } + } + catch (ActiveRecordException exc) + { + retries--; + if (retries <= 0) + throw; + } + catch (Exception exc) + { + throw; + } + + } } [ActiveRecord("Tags")] diff --git a/trunk/Pithos.Interfaces/FileInfoExtensions.cs b/trunk/Pithos.Interfaces/FileInfoExtensions.cs index 65f5703..a4c432e 100644 --- a/trunk/Pithos.Interfaces/FileInfoExtensions.cs +++ b/trunk/Pithos.Interfaces/FileInfoExtensions.cs @@ -74,8 +74,24 @@ namespace Pithos.Interfaces var parentDirInfo = dirInfo.Parent; if (null == parentDirInfo) return dirInfo.Name; - return Path.Combine(GetProperDirectoryCapitalization(parentDirInfo.FullName), - parentDirInfo.GetDirectories(dirInfo.Name)[0].Name); + + try + { + + + if (dirInfo.Exists) + return Path.Combine(GetProperDirectoryCapitalization(parentDirInfo.FullName), + parentDirInfo.GetDirectories(dirInfo.Name)[0].Name); + else + { + return dirInfo.FullName; + } + } + catch (DirectoryNotFoundException) + { + //An exception can occur if a directory is deleted right after the Exists call + return dirInfo.FullName; + } } @@ -104,8 +120,23 @@ namespace Pithos.Interfaces //Directory will not be null for an absolute path Contract.Assume(dirInfo != null); - return Path.Combine(GetProperDirectoryCapitalization(dirInfo.FullName), - dirInfo.GetFiles(fileInfo.Name)[0].Name); + try + { + + if (fileInfo.Exists) + return Path.Combine(GetProperDirectoryCapitalization(dirInfo.FullName), + dirInfo.GetFiles(fileInfo.Name)[0].Name); + else + { + return fileInfo.FullName; + } + } + catch (FileNotFoundException) + { + //An exception can occur if a file is deleted right after the Exists call + return fileInfo.FullName; + + } } public static string GetProperCapitalization(this FileSystemInfo info) -- 1.7.10.4