From 039a89e5c8b3401963459b9d246d9dacd2e48088 Mon Sep 17 00:00:00 2001 From: Panagiotis Kanavos Date: Tue, 10 Jan 2012 23:28:58 +0200 Subject: [PATCH] Further changes to reduce locking and switch to WAL journal mode for SQLite Also added dictionary with deleted files with dates to filter upload/download of deleted files. Should probably add a deletion check when commiting downloaded files --- trunk/Pithos.Client.WPF/Properties/AssemblyInfo.cs | 4 +- trunk/Pithos.Core.Test/MockStatusKeeper.cs | 5 + trunk/Pithos.Core/Agents/CloudTransferAction.cs | 2 + trunk/Pithos.Core/Agents/NetworkAgent.cs | 55 ++- trunk/Pithos.Core/Agents/StatusAgent.cs | 354 ++++++++++++-------- trunk/Pithos.Core/Agents/WorkflowAgent.cs | 3 +- trunk/Pithos.Core/FileState.cs | 5 +- trunk/Pithos.Core/IStatusKeeper.cs | 9 + trunk/Pithos.Interfaces/FileInfoExtensions.cs | 6 +- 9 files changed, 286 insertions(+), 157 deletions(-) diff --git a/trunk/Pithos.Client.WPF/Properties/AssemblyInfo.cs b/trunk/Pithos.Client.WPF/Properties/AssemblyInfo.cs index 4ad171f..8cdb5d4 100644 --- a/trunk/Pithos.Client.WPF/Properties/AssemblyInfo.cs +++ b/trunk/Pithos.Client.WPF/Properties/AssemblyInfo.cs @@ -15,7 +15,7 @@ using System.Windows; [assembly: AssemblyCopyright("Copyright © GRNet 2011")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyInformationalVersion("2012-01-08")] +[assembly: AssemblyInformationalVersion("2012-01-10")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -52,4 +52,4 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.27000")] +[assembly: AssemblyVersion("1.0.0.27002")] diff --git a/trunk/Pithos.Core.Test/MockStatusKeeper.cs b/trunk/Pithos.Core.Test/MockStatusKeeper.cs index 07e807a..f2869e8 100644 --- a/trunk/Pithos.Core.Test/MockStatusKeeper.cs +++ b/trunk/Pithos.Core.Test/MockStatusKeeper.cs @@ -131,6 +131,11 @@ namespace Pithos.Core.Test throw new NotImplementedException(); } + public FileState GetStateByFilePath(string path) + { + throw new NotImplementedException(); + } + private PithosStatus _pithosStatus = PithosStatus.InSynch; public void SetPithosStatus(PithosStatus status) diff --git a/trunk/Pithos.Core/Agents/CloudTransferAction.cs b/trunk/Pithos.Core/Agents/CloudTransferAction.cs index cc16292..8366037 100644 --- a/trunk/Pithos.Core/Agents/CloudTransferAction.cs +++ b/trunk/Pithos.Core/Agents/CloudTransferAction.cs @@ -26,6 +26,8 @@ namespace Pithos.Core.Agents public FileState FileState { get; set; } public string Container { get; set; } + public readonly DateTime Created = DateTime.Now; + public Lazy LocalHash { get; protected set; } private Lazy _topHash; diff --git a/trunk/Pithos.Core/Agents/NetworkAgent.cs b/trunk/Pithos.Core/Agents/NetworkAgent.cs index 8c4792c..bef5599 100644 --- a/trunk/Pithos.Core/Agents/NetworkAgent.cs +++ b/trunk/Pithos.Core/Agents/NetworkAgent.cs @@ -60,7 +60,7 @@ namespace Pithos.Core.Agents //A separate agent is used to execute delete actions immediatelly; private Agent _deleteAgent; - + ConcurrentDictionary _deletedFiles=new ConcurrentDictionary(); [System.ComponentModel.Composition.Import] public IStatusKeeper StatusKeeper { get; set; } @@ -123,11 +123,13 @@ namespace Pithos.Core.Agents switch (action.Action) { case CloudActionType.UploadUnconditional: - await UploadCloudFile(action); + //Abort if the file was deleted before we reached this point + if (!IsDeletedFile(action)) + await UploadCloudFile(action); break; case CloudActionType.DownloadUnconditional: - - await DownloadCloudFile(accountInfo, cloudFile, downloadPath); + if (!IsDeletedFile(action)) + await DownloadCloudFile(accountInfo, cloudFile, downloadPath); break; case CloudActionType.DeleteCloud: //Redirect deletes to the delete agent @@ -135,16 +137,19 @@ namespace Pithos.Core.Agents break; case CloudActionType.RenameCloud: var moveAction = (CloudMoveAction)action; - RenameCloudFile(accountInfo, moveAction); + if (!IsDeletedFile(action)) + RenameCloudFile(accountInfo, moveAction); break; case CloudActionType.MustSynch: if (!File.Exists(downloadPath) && !Directory.Exists(downloadPath)) { - await DownloadCloudFile(accountInfo, cloudFile, downloadPath); + if (!IsDeletedFile(action)) + await DownloadCloudFile(accountInfo, cloudFile, downloadPath); } else { - await SyncFiles(accountInfo, action); + if (!IsDeletedFile(action)) + await SyncFiles(accountInfo, action); } break; } @@ -217,10 +222,11 @@ namespace Pithos.Core.Agents //agent using (var gate = NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting)) { - // Remove any related actions from the normal agent - _agent.Remove(queuedAction => - queuedAction.CloudFile.Container == action.CloudFile.Container && - queuedAction.CloudFile.Name == action.CloudFile.Name); + + //Add the file URL to the deleted files list + var key = GetFileKey(action.CloudFile); + _deletedFiles[key]=DateTime.Now; + // and then delete the file from the server DeleteCloudFile(accountInfo, cloudFile); Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile, @@ -259,6 +265,12 @@ namespace Pithos.Core.Agents } } + private static string GetFileKey(ObjectInfo info) + { + var key = String.Format("{0}/{1}/{2}", info.Account, info.Container,info.Name); + return key; + } + private async Task SyncFiles(AccountInfo accountInfo,CloudAction action) { if (accountInfo == null) @@ -551,7 +563,8 @@ namespace Pithos.Core.Agents continue; using (new SessionScope(FlushAction.Never)) { - var state = FileState.FindByFilePath(localFile.FullName); + var state = StatusKeeper.GetStateByFilePath(localFile.FullName); + //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, @@ -695,6 +708,7 @@ namespace Pithos.Core.Agents if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase)) return; + //Are we already downloading or uploading the file? using (var gate=NetworkGate.Acquire(localPath, NetworkOperation.Downloading)) { @@ -884,7 +898,7 @@ namespace Pithos.Core.Agents if (fileInfo.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)) return; - + var relativePath = fileInfo.AsRelativeTo(accountInfo.AccountPath); if (relativePath.StartsWith(FolderConstants.OthersFolder)) { @@ -994,6 +1008,21 @@ namespace Pithos.Core.Agents } + private bool IsDeletedFile(CloudAction action) + { + var key = GetFileKey(action.CloudFile); + DateTime entryDate; + if (_deletedFiles.TryGetValue(key, out entryDate)) + { + //If the delete entry was created after this action, abort the action + if (entryDate > action.Created) + return true; + //Otherwise, remove the stale entry + _deletedFiles.TryRemove(key, out entryDate); + } + return false; + } + private bool HandleUploadWebException(CloudAction action, WebException exc) { var response = exc.Response as HttpWebResponse; diff --git a/trunk/Pithos.Core/Agents/StatusAgent.cs b/trunk/Pithos.Core/Agents/StatusAgent.cs index 0f99f86..974f983 100644 --- a/trunk/Pithos.Core/Agents/StatusAgent.cs +++ b/trunk/Pithos.Core/Agents/StatusAgent.cs @@ -42,6 +42,8 @@ namespace Pithos.Core.Agents ActiveRecordStarter.CreateSchema(); } + + private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath) { if (String.IsNullOrWhiteSpace(pithosDbPath)) @@ -174,85 +176,99 @@ namespace Pithos.Core.Agents private int UpdateStatusDirect(Guid id, FileStatus status) { - try + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) { - var connectionString = GetConnectionString(); - using (var connection = new SQLiteConnection(connectionString)) - using (var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id ", - connection)) + try { + using (var connection = GetConnection()) + using ( + var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id ", + connection)) + { + command.Parameters.AddWithValue("fileStatus", status); + + command.Parameters.AddWithValue("id", id); + + var affected = command.ExecuteNonQuery(); + + return affected; + } - 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; } } - catch (Exception exc) - { - Log.Error(exc.ToString()); - throw; - } - } private int UpdateStatusDirect(string path, FileStatus status) { - try + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) { - var connectionString = GetConnectionString(); - using (var connection = new SQLiteConnection(connectionString)) - using (var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path ", - connection)) + try { + + using (var connection = GetConnection()) + using ( + 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; + command.Parameters.AddWithValue("fileStatus", status); + + command.Parameters.AddWithValue("path", path.ToLower()); + + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; } } - catch (Exception exc) - { - Log.Error(exc.ToString()); - throw; - } - } private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus) { - try + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) { - var connectionString = GetConnectionString(); - using (var connection = new SQLiteConnection(connectionString)) - using (var command = new SQLiteCommand("update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path ", - connection)) + try { + - command.Parameters.AddWithValue("path", absolutePath.ToLower()); - command.Parameters.AddWithValue("fileStatus", fileStatus); - command.Parameters.AddWithValue("overlayStatus", overlayStatus); - connection.Open(); - var affected = command.ExecuteNonQuery(); - return affected; + using (var connection = GetConnection()) + using ( + 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); + + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; } } - catch (Exception exc) - { - Log.Error(exc.ToString()); - throw; - } - } @@ -292,6 +308,74 @@ namespace Pithos.Core.Agents private readonly string _pithosDataPath; + public FileState GetStateByFilePath(string path) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + + try + { + + using (var connection = GetConnection()) + using (var command = new SQLiteCommand("select Id, FilePath, OverlayStatus,FileStatus ,Checksum ,Version ,VersionTimeStamp,IsShared ,SharedBy ,ShareWrite from FileState where FilePath=:path", connection)) + { + + command.Parameters.AddWithValue("path", path.ToLower()); + + using (var reader = command.ExecuteReader()) + { + if (reader.Read()) + { + //var values = new object[reader.FieldCount]; + //reader.GetValues(values); + var state = new FileState + { + Id = reader.GetGuid(0), + FilePath = reader.IsDBNull(1)?"":reader.GetString(1), + OverlayStatus =reader.IsDBNull(2)?FileOverlayStatus.Unversioned: (FileOverlayStatus) reader.GetInt64(2), + FileStatus = reader.IsDBNull(3)?FileStatus.Missing:(FileStatus) reader.GetInt64(3), + Checksum = reader.IsDBNull(4)?"":reader.GetString(4), + Version = reader.IsDBNull(5)?default(long):reader.GetInt64(5), + VersionTimeStamp = reader.IsDBNull(6)?default(DateTime):reader.GetDateTime(6), + IsShared = !reader.IsDBNull(7) && reader.GetBoolean(7), + SharedBy = reader.IsDBNull(8)?"":reader.GetString(8), + ShareWrite = !reader.IsDBNull(9) && reader.GetBoolean(9) + }; +/* + var state = new FileState + { + Id = (Guid) values[0], + FilePath = (string) values[1], + OverlayStatus = (FileOverlayStatus) (long)values[2], + FileStatus = (FileStatus) (long)values[3], + Checksum = (string) values[4], + Version = (long?) values[5], + VersionTimeStamp = (DateTime?) values[6], + IsShared = (long)values[7] == 1, + SharedBy = (string) values[8], + ShareWrite = (long)values[9] == 1 + }; +*/ + return state; + } + else + { + return null; + } + + } + } + } + catch (Exception exc) + { + Log.ErrorFormat(exc.ToString()); + throw; + } + } + public FileOverlayStatus GetFileOverlayStatus(string path) { if (String.IsNullOrWhiteSpace(path)) @@ -302,13 +386,13 @@ namespace Pithos.Core.Agents try { - var connectionString = GetConnectionString(); - using (var connection = new SQLiteConnection(connectionString)) + + using (var connection = GetConnection()) using (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); } @@ -322,10 +406,23 @@ 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); + var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True;Default Isolation Level=ReadCommited;show_sql=true", _pithosDataPath); return connectionString; } + private SQLiteConnection GetConnection() + { + var connectionString = GetConnectionString(); + var connection = new SQLiteConnection(connectionString); + connection.Open(); + using(var cmd =connection.CreateCommand()) + { + cmd.CommandText = "PRAGMA journal_mode=WAL"; + cmd.ExecuteNonQuery(); + } + return connection; + } + public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus) { if (String.IsNullOrWhiteSpace(path)) @@ -366,6 +463,7 @@ namespace Pithos.Core.Agents _persistenceAgent.Post(() => UpdateStatusDirect(path.ToLower(), fileStatus, overlayStatus)); } +/* public void StoreInfo(string path,ObjectInfo objectInfo) { if (String.IsNullOrWhiteSpace(path)) @@ -404,7 +502,9 @@ namespace Pithos.Core.Agents }); } - public void StoreInfoDirect(string path, ObjectInfo objectInfo) +*/ + + public void StoreInfo(string path, ObjectInfo objectInfo) { if (String.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path"); @@ -414,76 +514,57 @@ namespace Pithos.Core.Agents throw new ArgumentNullException("objectInfo", "objectInfo can't be empty"); Contract.EndContractBlock(); - _persistenceAgent.Post(() => - { - try - { - - var connectionString = GetConnectionString(); - using (var connection = new SQLiteConnection(connectionString)) - { - if (StateExists(path)) - var command = - new SQLiteCommand( - "update FileState set FileStatus= :fileStatus where Id = :id ", - connection); - - - var filePath = path.ToLower(); - //Load the existing files state and set its properties in one session - using (new SessionScope()) - { - //Forgetting to use a sessionscope results in two sessions being created, one by - //FirstOrDefault and one by Save() - var state = FileState.FindByFilePath(filePath); - - //Create a new empty state object if this is a new file - state = state ?? new FileState(); - - state.FilePath = filePath; - state.Checksum = objectInfo.Hash; - state.Version = objectInfo.Version; - state.VersionTimeStamp = objectInfo.VersionTimestamp; - - state.FileStatus = FileStatus.Unchanged; - state.OverlayStatus = FileOverlayStatus.Normal; - - - //Do the save - state.Save(); - } - - - command.Parameters.AddWithValue("fileStatus", status); - - command.Parameters.AddWithValue("id", id); - connection.Open(); - var affected = command.ExecuteNonQuery(); - return; - } - } - catch (Exception exc) - { - Log.Error(exc.ToString()); - throw; - } - - }); + _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo)); } + private void StoreInfoDirect(string path, ObjectInfo objectInfo) + { + try + { + + using (var connection = GetConnection()) + using (var command = new SQLiteCommand(connection)) + { + if (StateExists(path, connection)) + command.CommandText = + "update FileState set FileStatus= :fileStatus where FilePath = :path "; + else + { + command.CommandText = + "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,FileStatus,OverlayStatus) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:fileStatus,:overlayStatus)"; + command.Parameters.AddWithValue("id", Guid.NewGuid()); + } + + command.Parameters.AddWithValue("path", path.ToLower()); + command.Parameters.AddWithValue("checksum", objectInfo.Hash); + command.Parameters.AddWithValue("version", objectInfo.Version); + command.Parameters.AddWithValue("versionTimeStamp", + objectInfo.VersionTimestamp); + command.Parameters.AddWithValue("fileStatus", FileStatus.Unchanged); + command.Parameters.AddWithValue("overlayStatus", + FileOverlayStatus.Normal); + + var affected = command.ExecuteNonQuery(); + return; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + private bool StateExists(string filePath,SQLiteConnection connection) { - using(var command=new SQLiteCommand("Select count(*) from FileState where FilePath=:path",connection)) + using (var command = new SQLiteCommand("Select count(*) from FileState where FilePath=:path", connection)) { command.Parameters.AddWithValue("path", filePath.ToLower()); - var result=command.ExecuteScalar(); - new SQLiteCommand( - "update FileState set FileStatus= :fileStatus where Id = :id ", - connection); + var result = command.ExecuteScalar(); + return ((long)result >= 1); + } - - } public void SetFileStatus(string path, FileStatus status) @@ -505,12 +586,12 @@ namespace Pithos.Core.Agents throw new ArgumentException("The path must be rooted", "path"); Contract.EndContractBlock(); - var connectionString = GetConnectionString(); - using (var connection = new SQLiteConnection(connectionString)) + + using (var connection = GetConnection()) { 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); } @@ -529,27 +610,30 @@ namespace Pithos.Core.Agents private int DeleteDirect(string filePath) { - try + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect")) { - var connectionString = GetConnectionString(); - using (var connection = new SQLiteConnection(connectionString)) + try { - var command = new SQLiteCommand("delete FileState where FilePath = :path", - connection); - command.Parameters.AddWithValue("path", filePath.ToLower()); - connection.Open(); - var affected = command.ExecuteNonQuery(); - return affected; + + using (var connection = GetConnection()) + { + var command = new SQLiteCommand("delete from FileState where FilePath = :path", + connection); + + command.Parameters.AddWithValue("path", filePath.ToLower()); + + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; } } - 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 1112076..4217bc4 100644 --- a/trunk/Pithos.Core/Agents/WorkflowAgent.cs +++ b/trunk/Pithos.Core/Agents/WorkflowAgent.cs @@ -118,8 +118,7 @@ namespace Pithos.Core.Agents using (new SessionScope(FlushAction.Never)) { - var fileState = FileState.FindByFilePath(path); - + var fileState = StatusKeeper.GetStateByFilePath(path); switch (state.Status) { diff --git a/trunk/Pithos.Core/FileState.cs b/trunk/Pithos.Core/FileState.cs index 34d8559..519fbf2 100644 --- a/trunk/Pithos.Core/FileState.cs +++ b/trunk/Pithos.Core/FileState.cs @@ -107,7 +107,7 @@ namespace Pithos.Core } - public static FileState FindByFilePath(string absolutePath) + /*public static FileState FindByFilePath(string absolutePath) { if (string.IsNullOrWhiteSpace(absolutePath)) throw new ArgumentNullException("absolutePath"); @@ -126,7 +126,7 @@ namespace Pithos.Core } - } + }*/ /* public static void DeleteByFilePath(string absolutePath) { @@ -380,6 +380,7 @@ namespace Pithos.Core using (new SessionScope()) { Execute(call, state); + return; } } catch (ActiveRecordException ) diff --git a/trunk/Pithos.Core/IStatusKeeper.cs b/trunk/Pithos.Core/IStatusKeeper.cs index 56f3bba..d45548c 100644 --- a/trunk/Pithos.Core/IStatusKeeper.cs +++ b/trunk/Pithos.Core/IStatusKeeper.cs @@ -30,6 +30,7 @@ namespace Pithos.Core string BlockHash { get; set; } int BlockSize { get; set; } void ChangeRoots(string oldPath, string newPath); + FileState GetStateByFilePath(string path); } [ContractClassFor(typeof(IStatusKeeper))] @@ -163,5 +164,13 @@ namespace Pithos.Core Contract.Requires(!string.IsNullOrWhiteSpace(newPath)); Contract.Requires(Path.IsPathRooted(newPath)); } + + public FileState GetStateByFilePath(string path) + { + Contract.Requires(!String.IsNullOrWhiteSpace(path)); + Contract.Requires(Path.IsPathRooted(path)); + + return null; + } } } diff --git a/trunk/Pithos.Interfaces/FileInfoExtensions.cs b/trunk/Pithos.Interfaces/FileInfoExtensions.cs index d60880c..51aa46d 100644 --- a/trunk/Pithos.Interfaces/FileInfoExtensions.cs +++ b/trunk/Pithos.Interfaces/FileInfoExtensions.cs @@ -64,7 +64,7 @@ namespace Pithos.Interfaces var dirInfo = new DirectoryInfo(fileName); return dirInfo.GetProperCapitalization(); } -#Replace State.FindByPath update with direct update call + public static string GetProperCapitalization(this DirectoryInfo dirInfo) { @@ -99,10 +99,10 @@ namespace Pithos.Interfaces public static string GetProperFilePathCapitalization(string fileName) { - if (String.IsNullOrWhiteSpace(fileName)) - return String.Empty; Contract.Ensures(!String.IsNullOrWhiteSpace(Contract.Result())); Contract.EndContractBlock(); + if (String.IsNullOrWhiteSpace(fileName)) + return String.Empty; var fileInfo = new FileInfo(fileName); -- 1.7.10.4