Added check for last MD5 modification, to avoid redundant recalculation of MD5
authorpkanavos <pkanavos@gmail.com>
Sat, 23 Jun 2012 14:43:08 +0000 (17:43 +0300)
committerpkanavos <pkanavos@gmail.com>
Sat, 23 Jun 2012 14:43:08 +0000 (17:43 +0300)
22 files changed:
trunk/NetSparkle/NetSparkleAppCast.cs
trunk/Pithos.Client.WPF/FileProperties/ConflictResolver.cs
trunk/Pithos.Client.WPF/FileProperties/ConflictsView.xaml
trunk/Pithos.Client.WPF/FileProperties/ConflictsViewModel.cs
trunk/Pithos.Client.WPF/Preferences/PreferencesView.xaml
trunk/Pithos.Core.Test/MockStatusKeeper.cs
trunk/Pithos.Core.Test/NetworkAgentTest.cs
trunk/Pithos.Core/Agents/BlockExtensions.cs
trunk/Pithos.Core/Agents/CloudTransferAction.cs
trunk/Pithos.Core/Agents/Downloader.cs
trunk/Pithos.Core/Agents/FileAgent.cs
trunk/Pithos.Core/Agents/PollAgent.cs
trunk/Pithos.Core/Agents/StateTuple.cs
trunk/Pithos.Core/Agents/StatusAgent.cs
trunk/Pithos.Core/Agents/Uploader.cs
trunk/Pithos.Core/FileState.cs
trunk/Pithos.Core/IStatusKeeper.cs
trunk/Pithos.Core/WorkflowState.cs
trunk/Pithos.Network.Test/CloudFilesClientTest.cs
trunk/Pithos.Network.Test/SignatureTest.cs
trunk/Pithos.Network/BlockHashAlgorithms.cs
trunk/Pithos.Network/Signature.cs

index 03c057c..fbba6e3 100644 (file)
@@ -103,9 +103,11 @@ namespace AppLimit.NetSparkle
             }
 
             // add some other attributes
-            latestVersion.AppName = _config.ApplicationName;
-            latestVersion.AppVersionInstalled = _config.InstalledVersion;
-            
+            if (latestVersion != null)
+            {
+                latestVersion.AppName = _config.ApplicationName;
+                latestVersion.AppVersionInstalled = _config.InstalledVersion;
+            }
             // go ahead
             return latestVersion;
         }
index 1c66f24..2a7a1aa 100644 (file)
@@ -1,8 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel.Composition;
+using System.IO;
 using System.Linq;
 using System.Text;
+using System.Windows;
 using Caliburn.Micro;
 using Pithos.Core;
 using Pithos.Core.Agents;
@@ -21,8 +23,14 @@ namespace Pithos.Client.WPF.FileProperties
         public ShellViewModel Shell { get; set; }
 
         [Import]
+        public IStatusKeeper  StatusKeeper { get; set; }
+
+        [Import]
         public NetworkAgent NetworkAgent { get; set; }
 
+        [Import]
+        public IStatusNotification StatusNotification { get; set; }
+
         public void Resolve(IEnumerable<ConflictFile> conflicts)
         {
             KeepServer(conflicts.Where(c => c.Action == ConflictAction.KeepServer));
@@ -75,7 +83,21 @@ namespace Pithos.Client.WPF.FileProperties
                           let info = monitor.GetObjectInfo(conflict.FilePath)
                           select new CloudDownloadAction(account, info, "Resolver");
 
-            downloadActions.Apply(action=> StatusAgent.SetFileState(action.FileState.FilePath,FileStatus.Unchanged,FileOverlayStatus.Normal,"Resolve by downloading"));
+            foreach (var action in downloadActions)
+            {
+                try
+                {
+                    action.LocalFile.Delete();
+                    StatusAgent.SetFileState(action.FileState.FilePath, FileStatus.Unchanged, FileOverlayStatus.Normal, "Resolve by downloading");
+                }
+                catch (Exception exc)
+                {
+                    MessageBox.Show(
+                        String.Format("The file {0} is in use and can't be deleted", action.LocalFile.FullName),
+                        "Pithos+. Can't delete file");                    
+                }
+                
+            }
             //downloadActions.Apply(NetworkAgent.Post);            
 
         }
@@ -94,12 +116,30 @@ namespace Pithos.Client.WPF.FileProperties
                           let account = accounts.First(acc => conflict.FilePath.StartsWith(acc.AccountPath, StringComparison.InvariantCultureIgnoreCase))
                           let info = FileInfoExtensions.FromPath(conflict.FilePath)
                           select new CloudUploadAction(account, info, conflict.State, 
-                              account.BlockSize, account.BlockHash,"Resolver",false);
-
-            actions.Apply(action => StatusAgent.SetFileState(action.FileState.FilePath, FileStatus.Modified, FileOverlayStatus.Normal, "Resolve by downloading"));
+                              account.BlockSize, account.BlockHash,"Resolver",false,new Progress<double>());
+
+            foreach (var action in actions)
+            {
+                DeleteCloudFile(action);
+                StatusAgent.SetFileState(action.FileState.FilePath, FileStatus.Modified, FileOverlayStatus.Normal, "Resolve by downloading");
+                
+            }
+            
+            //If C!=S, C!=L, S!=L, C!=Null, S!=Null and we delete the server file, we will cause an upload of the new file
             //actions.Apply(NetworkAgent.Post);
         }
 
+        private void DeleteCloudFile(CloudUploadAction action)
+        {
+            using (StatusNotification.GetNotifier("Deleting server {0}", "Deleted server {0}", Path.GetFileName(action.LocalFile.Name)))
+            {
+
+                StatusKeeper.SetFileState(action.LocalFile.FullName, FileStatus.Deleted,
+                                          FileOverlayStatus.Deleted, "");
+                NetworkAgent.DeleteAgent.DeleteCloudFile(action.AccountInfo, action.CloudFile);
+                StatusKeeper.ClearFileStatus(action.LocalFile.FullName);
+            }
+        }
         //Keeping both versions means that we need to copy one of them
         //somewhere and keep the other 
         private void KeepBoth(IEnumerable<ConflictFile> conflicts)
index a3bf9a7..7e4382d 100644 (file)
@@ -39,7 +39,9 @@
                     <Grid  >
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition />
+<!--
                             <ColumnDefinition Width="150"/>
+-->
                         </Grid.ColumnDefinitions>
                         <StackPanel Grid.Column="0" Margin="0,5">
                             <TextBlock x:Name="FilePath" Text="{Binding FilePath}" Margin="5,0"/>
                                 <TextBlock FontStyle="Italic" Margin="5,0">Local Date:<TextBlock x:Name="LocalModified" FontStyle="Italic"  Text="{Binding LocalModified}" Margin="5,0"/> </TextBlock>
                             </StackPanel>
                         </StackPanel>
+<!--
                         <ComboBox Grid.Column="1"  x:Name="Action" Margin="5"
                                   ItemsSource="{Binding Source={StaticResource ActionsList}}"                                                                                 
                                     SelectedValue="{Binding Action}" VerticalAlignment="Top"
                                   />
+-->
                     </Grid>
 
                 </DataTemplate>
@@ -87,7 +91,9 @@
         <StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
             <Button Name="Refresh" Content="Refresh" Margin="5,5,80,5" Style="{StaticResource ButtonStyle}" IsDefault="False" />
             <Button Name="Apply" Content="OK" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}" IsDefault="False" />
+<!--
             <Button Name="Cancel" Content="Cancel" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}" IsCancel="True" />
+-->
         </StackPanel>
 
     </Grid>
index e411e1c..8b133d9 100644 (file)
@@ -169,10 +169,10 @@ namespace Pithos.Client.WPF.FileProperties
 
         public void Apply()
         {
-            var conflicts = from conflict in Conflicts
+           /* var conflicts = from conflict in Conflicts
                             where conflict.Action != ConflictAction.Defer
                             select conflict;
-            Resolver.Resolve(conflicts);
+            Resolver.Resolve(conflicts);*/
             
             TryClose();
         }
index 0c2a608..dc103ad 100644 (file)
                             <CheckBox Name="CurrentAccount_SelectiveSyncEnabled" Content="Selective Sync Enabled" Grid.Row="7" Grid.Column="1"/>
                             <StackPanel Orientation="Horizontal" Grid.Row="8" Grid.Column="1">
                                 <Button Name="SelectiveSyncFolders" Width="100" Style="{StaticResource ButtonStyle}" Content="Selective Sync" />
-                                <Button Name="MoveAccountFolder" Content="Move ..." Width="100" Style="{StaticResource ButtonStyle}" Margin="20,5,5,5"/>
+                                <Button Name="MoveAccountFolder" Content="Move ..." Width="100" Style="{StaticResource ButtonStyle}" Margin="20,5,5,5" Visibility="Hidden"/>
                                 <Button Name="ClearAccountCache" Content="Clear Cache" Width="100" Style="{StaticResource ButtonStyle}"/>
                             </StackPanel>
                         </Grid>
index da1634b..73a4efa 100644 (file)
@@ -167,7 +167,7 @@ namespace Pithos.Core.Test
             return _pithosStatus;
         }
 
-        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string shortHash = null)
+        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string etag = null)
         {
             _overlayCache[path] = overlayStatus;
             return Task.Factory.StartNew(()=>{});
@@ -187,7 +187,7 @@ namespace Pithos.Core.Test
             _overlayCache.TryRemove(oldPath, out value);
         }
 
-        public void UpdateFileChecksum(string path, string shortHash, string checksum)
+        public void UpdateFileChecksum(string path, string etag, string checksum)
         {
             _checksums[path] = checksum;
         }
@@ -221,5 +221,10 @@ namespace Pithos.Core.Test
         {
             throw new NotImplementedException();
         }
+
+        public void UpdateLastMD5(FileInfo path, string etag)
+        {
+            throw new NotImplementedException();
+        }
     }
 }
index f61d6c2..e732673 100644 (file)
@@ -52,7 +52,7 @@ namespace Pithos.Core.Test
 
             client.DeleteObject(null, FolderConstants.PithosContainer, fileName);
 
-            var treeHash =  Signature.CalculateTreeHashAsync(filePath, accountInfo.BlockSize, accountInfo.BlockHash, 2);
+            var treeHash = Signature.CalculateTreeHashAsync(filePath, accountInfo.BlockSize, accountInfo.BlockHash, 2, new Progress<double>());
             var cloudFile = new ObjectInfo {Account = account, Container = "pithos"};
             var fileInfo = new FileInfo(filePath);
 
@@ -99,7 +99,7 @@ namespace Pithos.Core.Test
                 .Wait();
 
             Assert.IsTrue(File.Exists(filePath));
-            var treeHash = Signature.CalculateTreeHashAsync(filePath, accountInfo.BlockSize, accountInfo.BlockHash, 2);
+            var treeHash = Signature.CalculateTreeHashAsync(filePath, accountInfo.BlockSize, accountInfo.BlockHash, 2, new Progress<double>());
 
             Assert.AreEqual(treeHash.TopHash, newHash.TopHash);
 
index ac38e3d..8d63ba9 100644 (file)
@@ -73,7 +73,7 @@ namespace Pithos.Core.Agents
             }
         }
 
-       public static string CalculateHash(this FileSystemInfo info,int blockSize,string algorithm)
+       public static string CalculateHash(this FileSystemInfo info,int blockSize,string algorithm,IProgress<double> progress )
         {
             if (info==null)
                 throw new ArgumentNullException("info");
@@ -92,7 +92,7 @@ namespace Pithos.Core.Agents
            if (!info.Exists)
                return String.Empty;
 
-           return Signature.CalculateTreeHash(info.FullName, blockSize, algorithm).TopHash.ToHashString();
+           return Signature.CalculateTreeHash(info.FullName, blockSize, algorithm,progress).TopHash.ToHashString();
 
         }
 
index 9f24195..0248e89 100644 (file)
@@ -99,7 +99,7 @@ namespace Pithos.Core.Agents
             Originator = originator;
         }
 
-        public CloudAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo localFile, ObjectInfo cloudFile, FileState state, int blockSize, string algorithm,object originator)
+        public CloudAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo localFile, ObjectInfo cloudFile, FileState state, int blockSize, string algorithm,object originator,IProgress<double> progress )
             : this(accountInfo,action,originator)
         {
             if(blockSize<=0)
@@ -112,7 +112,7 @@ namespace Pithos.Core.Agents
             if (LocalFile == null) 
                 return;
 
-            TreeHash = new Lazy<TreeHash>(() => Signature.CalculateTreeHash(LocalFile, blockSize,algorithm),
+            TreeHash = new Lazy<TreeHash>(() => Signature.CalculateTreeHash(LocalFile, blockSize,algorithm,progress),
                                             LazyThreadSafetyMode.ExecutionAndPublication);
         }
 
@@ -230,8 +230,8 @@ namespace Pithos.Core.Agents
 
         public bool IsCreation { get; set; }
 
-        public CloudUploadAction(AccountInfo accountInfo, FileSystemInfo fileInfo, FileState state, int blockSize, string algorithm,object originator,bool isCreation)
-            : base(accountInfo, CloudActionType.UploadUnconditional,fileInfo,CreateObjectInfoFor(accountInfo,fileInfo),state,blockSize,algorithm,originator)
+        public CloudUploadAction(AccountInfo accountInfo, FileSystemInfo fileInfo, FileState state, int blockSize, string algorithm,object originator,bool isCreation,IProgress<double> progress )
+            : base(accountInfo, CloudActionType.UploadUnconditional,fileInfo,CreateObjectInfoFor(accountInfo,fileInfo),state,blockSize,algorithm,originator,progress)
         {
             IsCreation = isCreation;
         }
index d0419b8..75f1844 100644 (file)
@@ -56,14 +56,20 @@ namespace Pithos.Core.Agents
 \r
                     if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken).ConfigureAwait(false))\r
                         return;\r
-                \r
+\r
+                    var fileName = Path.GetFileName(filePath);\r
+                    var progress = new Progress<double>(d =>\r
+                        StatusNotification.Notify(new StatusNotification(String.Format("Hashing for Download {0} of {1}", d, fileName))));\r
+\r
+\r
                     TreeHash localTreeHash;\r
-                    using (StatusNotification.GetNotifier("Hashing for Download {0}", "Hashed for Download {0}", Path.GetFileName(filePath)))\r
+                    \r
+                    using (StatusNotification.GetNotifier("Hashing for Download {0}", "Hashed for Download {0}", fileName))\r
                     {\r
 \r
                         localTreeHash = Signature.CalculateTreeHashAsync(filePath,\r
                                                                             accountInfo.BlockSize,\r
-                                                                            accountInfo.BlockHash, 1);\r
+                                                                            accountInfo.BlockHash, 1,progress);\r
                     }\r
 \r
                     var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);\r
@@ -175,8 +181,12 @@ namespace Pithos.Core.Agents
             StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Calculating hashmap for {0} before download", Path.GetFileName(localPath)));\r
             //Calculate the file's treehash\r
 \r
+            var fileName = Path.GetFileName(localPath);\r
+            var progress = new Progress<double>(d =>\r
+                StatusNotification.Notify(new StatusNotification(String.Format("Hashing for Download {0} of {1}", d, fileName))));\r
+\r
             //TODO: Should pass cancellation token here\r
-            var treeHash = localTreeHash ?? Signature.CalculateTreeHashAsync(localPath, (int)serverHash.BlockSize, serverHash.BlockHash, 2);\r
+            var treeHash = localTreeHash ?? Signature.CalculateTreeHashAsync(localPath, (int)serverHash.BlockSize, serverHash.BlockHash, 2,progress);\r
 \r
             //And compare it with the server's hash\r
             var upHashes = serverHash.GetHashesAsStrings();\r
@@ -331,9 +341,9 @@ namespace Pithos.Core.Agents
                 return true;\r
 \r
             var info = new FileInfo(localPath);\r
-            var shortHash = info.ComputeShortHash(StatusNotification);\r
+            var etag = info.ComputeShortHash(StatusNotification);\r
             //If the file is different from the stored state, we have a change\r
-            if (localState.ShortHash != shortHash)\r
+            if (localState.ETag != etag)\r
                 return true;\r
             //If the top hashes differ, we have a change\r
             return (localState.Checksum != cloudFile.X_Object_Hash);\r
index 2c722d5..12ac257 100644 (file)
@@ -252,7 +252,7 @@ namespace Pithos.Core.Agents
                 
                 UpdateFileStatus(state);
                 UpdateOverlayStatus(state);
-                UpdateFileChecksum(state);
+                UpdateLastMD5(state);
                 WorkflowAgent.Post(state);
             }
             catch (IOException exc)
@@ -570,20 +570,20 @@ namespace Pithos.Core.Agents
             switch (state.Status)
             {
                 case FileStatus.Created:
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash).Wait();
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ETag).Wait();
                     break;
                 case FileStatus.Modified:
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ShortHash).Wait();
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();
                     break;
                 case FileStatus.Deleted:
                     //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
                     break;
                 case FileStatus.Renamed:
                     this.StatusKeeper.ClearFileStatus(state.OldPath);
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ShortHash).Wait();
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();
                     break;
                 case FileStatus.Unchanged:
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ShortHash).Wait();
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ETag).Wait();
                     break;
             }
 
@@ -613,10 +613,13 @@ namespace Pithos.Core.Agents
             using (StatusNotification.GetNotifier("Hashing {0}", "Finished Hashing {0}", info.Name))
             {
 
-                var shortHash = info.ComputeShortHash(StatusNotification);
+                var etag = info.ComputeShortHash(StatusNotification);
 
-                string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash);
-                StatusKeeper.UpdateFileChecksum(path, shortHash, merkleHash);
+                var progress = new Progress<double>(d =>
+                    StatusNotification.Notify(new StatusNotification(String.Format("Hashing {0} of {1}", d, info.Name))));
+
+                string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash,progress);
+                StatusKeeper.UpdateFileChecksum(path, etag, merkleHash);
 
                 state.Hash = merkleHash;
                 return state;
index b3cece8..466c997 100644 (file)
@@ -368,7 +368,7 @@ namespace Pithos.Core.Agents
 \r
                         //Get the local files here                        \r
                         var agent = AgentLocator<FileAgent>.Get(accountInfo.AccountPath);                                                \r
-                        var files = await LoadLocalFileTuples(accountInfo, accountBatch);\r
+                        var files = LoadLocalFileTuples(accountInfo, accountBatch);\r
 \r
                         var states = FileState.Queryable.ToList();                        \r
                         \r
@@ -384,7 +384,7 @@ namespace Pithos.Core.Agents
 \r
                         //Process only the changes in the batch file, if one exists\r
                         var stateTuples = accountBatch==null?tuples:tuples.Where(t => accountBatch.Contains(t.FilePath));\r
-                        foreach (var tuple in stateTuples)\r
+                        foreach (var tuple in stateTuples.Where(s=>!s.Locked))\r
                         {\r
                             await _unPauseEvent.WaitAsync().ConfigureAwait(false);\r
 \r
@@ -418,13 +418,14 @@ namespace Pithos.Core.Agents
                 return nextSince;\r
             }\r
         }\r
+/*\r
 \r
         private static void SetMerkleHash(AccountInfo accountInfo, StateTuple tuple)\r
         {\r
             //The Merkle hash for directories is that of an empty buffer\r
             if (tuple.FileInfo is DirectoryInfo)\r
                 tuple.C = MERKLE_EMPTY;\r
-            else if (tuple.FileState != null && tuple.MD5 == tuple.FileState.ShortHash)\r
+            else if (tuple.FileState != null && tuple.MD5 == tuple.FileState.ETag)\r
             {\r
                 //If there is a state whose MD5 matches, load the merkle hash from the file state\r
                 //insteaf of calculating it\r
@@ -432,12 +433,13 @@ namespace Pithos.Core.Agents
             }\r
             else\r
             {\r
-                tuple.Merkle = Signature.CalculateTreeHashAsync((FileInfo)tuple.FileInfo, accountInfo.BlockSize, accountInfo.BlockHash,1);\r
+                tuple.Merkle = Signature.CalculateTreeHashAsync((FileInfo)tuple.FileInfo, accountInfo.BlockSize, accountInfo.BlockHash,1,progress);\r
                 //tuple.C=tuple.Merkle.TopHash.ToHashString();                \r
             }\r
         }\r
+*/\r
 \r
-        private async Task<List<Tuple<FileSystemInfo, string>>> LoadLocalFileTuples(AccountInfo accountInfo,IEnumerable<string> batch )\r
+        private IEnumerable<FileSystemInfo> LoadLocalFileTuples(AccountInfo accountInfo,IEnumerable<string> batch )\r
         {\r
             using (ThreadContext.Stacks["Account Files Hashing"].Push(accountInfo.UserName))\r
             {\r
@@ -446,57 +448,8 @@ namespace Pithos.Core.Agents
                                                         .EnumerateFileSystemInfos();\r
                 if (batchPaths.Count>0)\r
                     localInfos= localInfos.Where(fi => batchPaths.Contains(fi.FullName));\r
-                \r
-                //Use the queue to retry locked file hashing\r
-                var fileQueue = new ConcurrentQueue<FileSystemInfo>(localInfos);\r
-                \r
 \r
-                var results = new List<Tuple<FileSystemInfo, string>>();\r
-                var backoff = 0;\r
-                while (fileQueue.Count > 0)\r
-                {\r
-                    FileSystemInfo file;\r
-                    fileQueue.TryDequeue(out file);\r
-                    using (ThreadContext.Stacks["File"].Push(file.FullName))\r
-                    {\r
-                        try\r
-                        {\r
-                            //Replace MD5 here, do the calc while syncing individual files\r
-                            string hash ;\r
-                            if (file is DirectoryInfo)\r
-                                hash = MD5_EMPTY;\r
-                            else\r
-                            {\r
-                                //Wait in case the FileAgent has requested a Pause\r
-                                await _unPauseEvent.WaitAsync().ConfigureAwait(false);\r
-                                \r
-                                using (StatusNotification.GetNotifier("Hashing {0}", "", file.Name))\r
-                                {\r
-                                    hash = ((FileInfo)file).ComputeShortHash(StatusNotification);\r
-                                    backoff = 0;\r
-                                }\r
-                            }                            \r
-                            results.Add(Tuple.Create(file, hash));\r
-                        }\r
-                        catch (IOException exc)\r
-                        {\r
-                            Log.WarnFormat("[HASH] File in use, will retry [{0}]", exc);\r
-                            fileQueue.Enqueue(file);\r
-                            //If this is the only enqueued file                            \r
-                            if (fileQueue.Count != 1) continue;\r
-                            \r
-                            \r
-                            //Increase delay\r
-                            if (backoff<60000)\r
-                                backoff += 10000;\r
-                            //Pause Polling for the specified time\r
-                        }\r
-                        if (backoff>0)\r
-                            await PauseFor(backoff).ConfigureAwait(false);\r
-                    }\r
-                }\r
-\r
-                return results;\r
+                return localInfos;\r
             }\r
         }\r
 \r
@@ -515,151 +468,174 @@ namespace Pithos.Core.Agents
 \r
         private async Task SyncSingleItem(AccountInfo accountInfo, StateTuple tuple, FileAgent agent, CancellationToken token)\r
         {\r
-            Log.DebugFormat("Sync [{0}] C:[{1}] L:[{2}] S:[{3}]",tuple.FilePath,tuple.C,tuple.L,tuple.S);\r
+            Log.DebugFormat("Sync [{0}] C:[{1}] L:[{2}] S:[{3}]", tuple.FilePath, tuple.C, tuple.L, tuple.S);\r
+\r
+            try\r
+            {\r
 \r
-            var localFilePath = tuple.FilePath;\r
-            //Don't use the tuple info, it may have been deleted\r
-            var localInfo = FileInfoExtensions.FromPath(localFilePath);\r
+                var localFilePath = tuple.FilePath;\r
+                //Don't use the tuple info, it may have been deleted\r
+                var localInfo = FileInfoExtensions.FromPath(localFilePath);\r
 \r
 \r
-            var isUnselectedRootFolder = agent.IsUnselectedRootFolder(tuple.FilePath);\r
+                var isUnselectedRootFolder = agent.IsUnselectedRootFolder(tuple.FilePath);\r
 \r
-            //Unselected root folders that have not yet been uploaded should be uploaded and added to the \r
-            //selective folders\r
+                //Unselected root folders that have not yet been uploaded should be uploaded and added to the \r
+                //selective folders\r
 \r
-            if (!Selectives.IsSelected(accountInfo, localFilePath) && !(isUnselectedRootFolder && tuple.ObjectInfo==null) )                \r
-                return;\r
+                if (!Selectives.IsSelected(accountInfo, localFilePath) &&\r
+                    !(isUnselectedRootFolder && tuple.ObjectInfo == null))\r
+                    return;\r
 \r
-            // Local file unchanged? If both C and L are null, make sure it's because \r
-            //both the file is missing and the state checksum is not missing\r
-            if (tuple.C == tuple.L /*&& (localInfo.Exists || tuple.FileState == null)*/)\r
-            {\r
-                //No local changes\r
-                //Server unchanged?\r
-                if (tuple.S == tuple.L)\r
-                {\r
-                    // No server changes\r
-                    //Has the file been renamed on the server?\r
-                    MoveForServerMove(accountInfo, tuple);\r
-                }\r
-                else\r
+                // Local file unchanged? If both C and L are null, make sure it's because \r
+                //both the file is missing and the state checksum is not missing\r
+                if (tuple.C == tuple.L /*&& (localInfo.Exists || tuple.FileState == null)*/)\r
                 {\r
-                    //Different from server\r
-                    //Does the server file exist?\r
-                    if (tuple.S == null)\r
+                    //No local changes\r
+                    //Server unchanged?\r
+                    if (tuple.S == tuple.L)\r
                     {\r
-                        //Server file doesn't exist\r
-                        //deleteObjectFromLocal()\r
-                        using (StatusNotification.GetNotifier("Deleting local {0}", "Deleted local {0}", Path.GetFileName(localFilePath)))\r
+                        // No server changes\r
+                        //Has the file been renamed on the server?\r
+                        MoveForServerMove(accountInfo, tuple);\r
+                    }\r
+                    else\r
+                    {\r
+                        //Different from server\r
+                        //Does the server file exist?\r
+                        if (tuple.S == null)\r
                         {\r
-                            StatusKeeper.SetFileState(localFilePath, FileStatus.Deleted,\r
-                                                      FileOverlayStatus.Deleted, "");\r
-                            using (NetworkGate.Acquire(localFilePath, NetworkOperation.Deleting))\r
+                            //Server file doesn't exist\r
+                            //deleteObjectFromLocal()\r
+                            using (\r
+                                StatusNotification.GetNotifier("Deleting local {0}", "Deleted local {0}",\r
+                                                               Path.GetFileName(localFilePath)))\r
                             {\r
-                                agent.Delete(localFilePath);\r
+                                StatusKeeper.SetFileState(localFilePath, FileStatus.Deleted,\r
+                                                          FileOverlayStatus.Deleted, "");\r
+                                using (NetworkGate.Acquire(localFilePath, NetworkOperation.Deleting))\r
+                                {\r
+                                    agent.Delete(localFilePath);\r
+                                }\r
+                                //updateRecord(Remove C, L)\r
+                                StatusKeeper.ClearFileStatus(localFilePath);\r
                             }\r
-                            //updateRecord(Remove C, L)\r
-                            StatusKeeper.ClearFileStatus(localFilePath);\r
                         }\r
-                    }\r
-                    else\r
-                    {\r
-                        //Server file exists\r
-                        //downloadServerObject() // Result: L = S\r
-                        //If the file has moved on the server, move it locally before downloading\r
-                        using (StatusNotification.GetNotifier("Downloading {0}", "Downloaded {0}", Path.GetFileName(localFilePath)))\r
+                        else\r
                         {\r
-                            var targetPath = MoveForServerMove(accountInfo, tuple);\r
+                            //Server file exists\r
+                            //downloadServerObject() // Result: L = S\r
+                            //If the file has moved on the server, move it locally before downloading\r
+                            using (\r
+                                StatusNotification.GetNotifier("Downloading {0}", "Downloaded {0}",\r
+                                                               Path.GetFileName(localFilePath)))\r
+                            {\r
+                                var targetPath = MoveForServerMove(accountInfo, tuple);\r
 \r
-                            StatusKeeper.SetFileState(targetPath, FileStatus.Modified,FileOverlayStatus.Modified, "");\r
-                            \r
-                            await NetworkAgent.Downloader.DownloadCloudFile(accountInfo, tuple.ObjectInfo, targetPath, token)\r
-                                    .ConfigureAwait(false);\r
-                            //updateRecord( L = S )\r
-                            StatusKeeper.UpdateFileChecksum(targetPath, tuple.ObjectInfo.ETag,\r
-                                                            tuple.ObjectInfo.X_Object_Hash);\r
+                                StatusKeeper.SetFileState(targetPath, FileStatus.Modified, FileOverlayStatus.Modified,\r
+                                                          "");\r
 \r
-                            StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo);\r
-                        }\r
+                                await\r
+                                    NetworkAgent.Downloader.DownloadCloudFile(accountInfo, tuple.ObjectInfo, targetPath,\r
+                                                                              token)\r
+                                        .ConfigureAwait(false);\r
+                                //updateRecord( L = S )\r
+                                StatusKeeper.UpdateFileChecksum(targetPath, tuple.ObjectInfo.ETag,\r
+                                                                tuple.ObjectInfo.X_Object_Hash);\r
 \r
-                        /*\r
-                                                        StatusKeeper.SetFileState(targetPath, FileStatus.Unchanged,\r
-                                                                                  FileOverlayStatus.Normal, "");\r
-                            */\r
-                    }\r
-                }\r
+                                StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo);\r
+                            }\r
 \r
-            }\r
-            else\r
-            {\r
-                //Local changes found\r
+                            /*\r
+                                                            StatusKeeper.SetFileState(targetPath, FileStatus.Unchanged,\r
+                                                                                      FileOverlayStatus.Normal, "");\r
+                                */\r
+                        }\r
+                    }\r
 \r
-                //Server unchanged?\r
-                if (tuple.S == tuple.L)\r
+                }\r
+                else\r
                 {\r
-                    //The FileAgent selective sync checks for new root folder files\r
-                    if (!agent.Ignore(localFilePath))\r
+                    //Local changes found\r
+\r
+                    //Server unchanged?\r
+                    if (tuple.S == tuple.L)\r
                     {\r
-                        if ((tuple.C == null || !localInfo.Exists) && tuple.ObjectInfo != null)\r
-                        {\r
-                            //deleteObjectFromServer()\r
-                            DeleteCloudFile(accountInfo, tuple);\r
-                            //updateRecord( Remove L, S)                  \r
-                        }\r
-                        else\r
+                        //The FileAgent selective sync checks for new root folder files\r
+                        if (!agent.Ignore(localFilePath))\r
                         {\r
-                            //uploadLocalObject() // Result: S = C, L = S                        \r
-\r
-                            //Debug.Assert(tuple.FileState !=null);\r
-                            var action = new CloudUploadAction(accountInfo, localInfo, tuple.FileState,\r
-                                                               accountInfo.BlockSize, accountInfo.BlockHash,\r
-                                                               "Poll", isUnselectedRootFolder);\r
-                            using (StatusNotification.GetNotifier("Uploading {0}", "Uploaded {0}", Path.GetFileName(localFilePath)))\r
+                            if ((tuple.C == null || !localInfo.Exists) && tuple.ObjectInfo != null)\r
                             {\r
-                                await NetworkAgent.Uploader.UploadCloudFile(action, token).ConfigureAwait(false);\r
+                                //deleteObjectFromServer()\r
+                                DeleteCloudFile(accountInfo, tuple);\r
+                                //updateRecord( Remove L, S)                  \r
                             }\r
+                            else\r
+                            {\r
+                                //uploadLocalObject() // Result: S = C, L = S                        \r
+                                var progress = new Progress<double>(d =>\r
+                                    StatusNotification.Notify(new StatusNotification(String.Format("Merkle Hashing for Upload {0:p} of {1}", d, localInfo.Name))));\r
+\r
+                                //Debug.Assert(tuple.FileState !=null);\r
+                                var action = new CloudUploadAction(accountInfo, localInfo, tuple.FileState,\r
+                                                                   accountInfo.BlockSize, accountInfo.BlockHash,\r
+                                                                   "Poll", isUnselectedRootFolder,progress);\r
+                                using (\r
+                                    StatusNotification.GetNotifier("Uploading {0}", "Uploaded {0}",\r
+                                                                   Path.GetFileName(localFilePath)))\r
+                                {\r
+                                    await NetworkAgent.Uploader.UploadCloudFile(action, token).ConfigureAwait(false);\r
+                                }\r
 \r
-                            //updateRecord( S = C )\r
-                            //State updated by the uploader\r
+                                //updateRecord( S = C )\r
+                                //State updated by the uploader\r
 \r
-                            if (isUnselectedRootFolder)\r
-                            {\r
-                                ProcessChildren(accountInfo, tuple, agent, token);\r
+                                if (isUnselectedRootFolder)\r
+                                {\r
+                                    ProcessChildren(accountInfo, tuple, agent, token);\r
+                                }\r
                             }\r
                         }\r
                     }\r
-                }\r
-                else\r
-                {\r
-                    if (tuple.C == tuple.S)\r
-                    {\r
-                        // (Identical Changes) Result: L = S\r
-                        //doNothing()\r
-                        //Detect server moves\r
-                        var targetPath = MoveForServerMove(accountInfo, tuple);\r
-                        StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo);\r
-                    }\r
                     else\r
                     {\r
-                        if ((tuple.C == null || !localInfo.Exists) && tuple.ObjectInfo != null)\r
-                        {\r
-                            //deleteObjectFromServer()\r
-                            DeleteCloudFile(accountInfo, tuple);\r
-                            //updateRecord(Remove L, S)                  \r
-                        }\r
-                            //If both the local and server files are missing, the state is stale\r
-                        else if (!localInfo.Exists && (tuple.S == null || tuple.ObjectInfo == null))\r
+                        if (tuple.C == tuple.S)\r
                         {\r
-                            StatusKeeper.ClearFileStatus(localInfo.FullName);\r
+                            // (Identical Changes) Result: L = S\r
+                            //doNothing()\r
+                            //Detect server moves\r
+                            var targetPath = MoveForServerMove(accountInfo, tuple);\r
+                            StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo);\r
                         }\r
                         else\r
                         {\r
-                            ReportConflictForMismatch(localFilePath);\r
-                            //identifyAsConflict() // Manual action required\r
+                            if ((tuple.C == null || !localInfo.Exists) && tuple.ObjectInfo != null)\r
+                            {\r
+                                //deleteObjectFromServer()\r
+                                DeleteCloudFile(accountInfo, tuple);\r
+                                //updateRecord(Remove L, S)                  \r
+                            }\r
+                                //If both the local and server files are missing, the state is stale\r
+                            else if (!localInfo.Exists && (tuple.S == null || tuple.ObjectInfo == null))\r
+                            {\r
+                                StatusKeeper.ClearFileStatus(localInfo.FullName);\r
+                            }\r
+                            else\r
+                            {\r
+                                ReportConflictForMismatch(localFilePath);\r
+                                //identifyAsConflict() // Manual action required\r
+                            }\r
                         }\r
                     }\r
                 }\r
             }\r
+            catch (Exception exc)\r
+            {\r
+                //In case of error log and retry with the next poll\r
+                Log.ErrorFormat("[SYNC] Failed for file {0}. Will Retry.\r\n{1}",tuple.FilePath,exc);\r
+\r
+                \r
+            }\r
         }\r
 \r
         private string MoveForServerMove(AccountInfo accountInfo, StateTuple tuple)\r
@@ -720,25 +696,75 @@ namespace Pithos.Core.Agents
             fileTuples.ApplyAction(async t => await SyncSingleItem(accountInfo, t, agent, token).ConfigureAwait(false));\r
         }\r
 \r
-        private static IEnumerable<StateTuple> MergeSources(\r
+\r
+        /*\r
+         *                 //Use the queue to retry locked file hashing\r
+                var fileQueue = new ConcurrentQueue<FileSystemInfo>(localInfos);\r
+                \r
+\r
+                var results = new List<Tuple<FileSystemInfo, string>>();\r
+                var backoff = 0;\r
+                while (fileQueue.Count > 0)\r
+                {\r
+                    FileSystemInfo file;\r
+                    fileQueue.TryDequeue(out file);\r
+                    using (ThreadContext.Stacks["File"].Push(file.FullName))\r
+                    {\r
+                        try\r
+                        {\r
+                            //Replace MD5 here, do the calc while syncing individual files\r
+                            string hash ;\r
+                            if (file is DirectoryInfo)\r
+                                hash = MD5_EMPTY;\r
+                            else\r
+                            {\r
+                                //Wait in case the FileAgent has requested a Pause\r
+                                await _unPauseEvent.WaitAsync().ConfigureAwait(false);\r
+                                \r
+                                using (StatusNotification.GetNotifier("Hashing {0}", "", file.Name))\r
+                                {\r
+                                    hash = ((FileInfo)file).ComputeShortHash(StatusNotification);\r
+                                    backoff = 0;\r
+                                }\r
+                            }                            \r
+                            results.Add(Tuple.Create(file, hash));\r
+                        }\r
+                        catch (IOException exc)\r
+                        {\r
+                            Log.WarnFormat("[HASH] File in use, will retry [{0}]", exc);\r
+                            fileQueue.Enqueue(file);\r
+                            //If this is the only enqueued file                            \r
+                            if (fileQueue.Count != 1) continue;\r
+                            \r
+                            \r
+                            //Increase delay\r
+                            if (backoff<60000)\r
+                                backoff += 10000;\r
+                            //Pause Polling for the specified time\r
+                        }\r
+                        if (backoff>0)\r
+                            await PauseFor(backoff).ConfigureAwait(false);\r
+                    }\r
+                }\r
+\r
+                return results;\r
+\r
+         */\r
+        private IEnumerable<StateTuple> MergeSources(\r
             IEnumerable<Tuple<string, ObjectInfo>> infos, \r
-            IEnumerable<Tuple<FileSystemInfo, string>> files, \r
+            IEnumerable<FileSystemInfo> files, \r
             IEnumerable<FileState> states)\r
         {\r
-            var tuplesByPath = new Dictionary<string, StateTuple>();\r
-            foreach (var file in files)\r
-            {\r
-                var fsInfo = file.Item1;\r
-                var fileHash = fsInfo is DirectoryInfo? MD5_EMPTY:file.Item2;\r
+            var tuplesByPath = files.ToDictionary(f => f.FullName, f => new StateTuple {FileInfo = f}); new Dictionary<string, StateTuple>();\r
 \r
-                tuplesByPath[fsInfo.FullName] = new StateTuple {FileInfo = fsInfo, C=fileHash,MD5 = fileHash};\r
-            }\r
+            //For files that have state\r
             foreach (var state in states)\r
             {\r
                 StateTuple hashTuple;\r
                 if (tuplesByPath.TryGetValue(state.FilePath, out hashTuple))\r
                 {\r
                     hashTuple.FileState = state;\r
+                    UpdateMD5(hashTuple);\r
                 }\r
                 else\r
                 {\r
@@ -747,6 +773,11 @@ namespace Pithos.Core.Agents
                     tuplesByPath[state.FilePath] = hashTuple;\r
                 }\r
             }\r
+            //for files that don't have state\r
+            foreach (var tuple in tuplesByPath.Values.Where(t => t.FileState == null))\r
+            {\r
+                UpdateMD5(tuple);\r
+            }\r
 \r
             var tuplesByID = tuplesByPath.Values\r
                 .Where(tuple => tuple.FileState != null && tuple.FileState.ObjectID!=null)\r
@@ -779,6 +810,38 @@ namespace Pithos.Core.Agents
             return tuplesByPath.Values;\r
         }\r
 \r
+        private void  UpdateMD5(StateTuple hashTuple)\r
+        {\r
+            \r
+            try\r
+            {\r
+                var hash = Signature.MD5_EMPTY;\r
+                if (hashTuple.FileInfo is FileInfo)\r
+                {\r
+                    var file = hashTuple.FileInfo as FileInfo;\r
+                    var stateDate = hashTuple.NullSafe(h => h.FileState).NullSafe(s => s.LastWriteDate) ??\r
+                                    DateTime.MinValue;\r
+                    if (file.LastWriteTime - stateDate < TimeSpan.FromSeconds(1) &&\r
+                        hashTuple.FileState.LastLength == file.Length)\r
+                    {\r
+                        hash = hashTuple.FileState.LastMD5;\r
+                    }\r
+                    else\r
+                    {\r
+                        //Modified, must calculate hash\r
+                        hash = file.ComputeShortHash(StatusNotification);\r
+                        StatusKeeper.UpdateLastMD5(file, hash);\r
+                    }\r
+                }\r
+                hashTuple.C = hash;\r
+                hashTuple.MD5 = hash;\r
+            }\r
+            catch (IOException)\r
+            {\r
+                hashTuple.Locked = true;\r
+            }            \r
+        }\r
+\r
         /// <summary>\r
         /// Returns the latest LastModified date from the list of objects, but only if it is before\r
         /// than the threshold value\r
@@ -821,7 +884,6 @@ namespace Pithos.Core.Agents
         private bool _pause;\r
         \r
         const string MERKLE_EMPTY = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";\r
-        const string MD5_EMPTY = "d41d8cd98f00b204e9800998ecf8427e";\r
 \r
 \r
         private void ReportConflictForMismatch(string localFilePath)\r
index 948f029..5876fd8 100644 (file)
@@ -17,7 +17,7 @@ namespace Pithos.Core.Agents
         {
             get
             {
-                var hash = FileState.NullSafe(f => f.ShortHash);
+                var hash = FileState.NullSafe(f => f.ETag);
                 return String.IsNullOrWhiteSpace(hash) ? null : hash;
             }
         }
@@ -68,6 +68,8 @@ namespace Pithos.Core.Agents
             }
         }
 
+        public bool Locked { get; set; }
+
         public StateTuple() { }
 
         public StateTuple(FileSystemInfo info)
index 595ccbe..df9042c 100644 (file)
@@ -298,7 +298,7 @@ namespace Pithos.Core.Agents
                     //dictionary
 
                     //If the hashes don't match the file was changed
-                    if (fileState.ShortHash != hashString)
+                    if (fileState.ETag != hashString)
                     {
                         _persistenceAgent.Post(() => UpdateStatusDirect((Guid) fileState.Id, FileStatus.Modified));
                     }
@@ -457,7 +457,7 @@ namespace Pithos.Core.Agents
             {
                 
                 using (var connection = GetConnection())
-                using (var command = new SQLiteCommand("select Id, FilePath, OverlayStatus,FileStatus ,Checksum ,ShortHash,Version    ,VersionTimeStamp,IsShared   ,SharedBy   ,ShareWrite  from FileState where FilePath=:path COLLATE NOCASE", connection))
+                using (var command = new SQLiteCommand("select Id, FilePath, OverlayStatus,FileStatus ,Checksum ,ETag,Version    ,VersionTimeStamp,IsShared   ,SharedBy   ,ShareWrite  from FileState where FilePath=:path COLLATE NOCASE", connection))
                 {
                     
                     command.Parameters.AddWithValue("path", path);
@@ -475,7 +475,7 @@ namespace Pithos.Core.Agents
                                                 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),
-                                                ShortHash= reader.IsDBNull(5)?"":reader.GetString(5),
+                                                ETag= reader.IsDBNull(5)?"":reader.GetString(5),
                                                 Version = reader.IsDBNull(6)?default(long):reader.GetInt64(6),
                                                 VersionTimeStamp = reader.IsDBNull(7)?default(DateTime):reader.GetDateTime(7),
                                                 IsShared = !reader.IsDBNull(8) && reader.GetBoolean(8),
@@ -572,7 +572,7 @@ namespace Pithos.Core.Agents
             _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus));
         }*/
 
-        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string shortHash = null)
+        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string etag = null)
         {
             if (String.IsNullOrWhiteSpace(path))
                 throw new ArgumentNullException("path");
@@ -580,7 +580,7 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("The path must be rooted","path");
             Contract.EndContractBlock();
 
-            return _persistenceAgent.PostAndAwait(() => FileState.StoreOverlayStatus(path,overlayStatus,shortHash));
+            return _persistenceAgent.PostAndAwait(() => FileState.StoreOverlayStatus(path,overlayStatus,etag));
         }
 
        /* public void RenameFileOverlayStatus(string oldPath, string newPath)
@@ -678,21 +678,21 @@ namespace Pithos.Core.Agents
                     //If the ID exists, update the status
                     if (StateExistsByID(objectInfo.UUID,connection))
                         command.CommandText =
-                            "update FileState set FilePath=:path,FileStatus= :fileStatus, Checksum=:checksum, ShortHash=:shortHash,Version=:version,VersionTimeStamp=:versionTimeStamp where ObjectID = :objectID  ";                        
+                            "update FileState set FilePath=:path,FileStatus= :fileStatus, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where ObjectID = :objectID  ";                        
                     else if (StateExists(path, connection))
                         //If the ID doesn't exist, try to update using the path, and store the ID as well.
                         command.CommandText =
-                            "update FileState set FileStatus= :fileStatus, ObjectID=:objectID, Checksum=:checksum, ShortHash=:shortHash,Version=:version,VersionTimeStamp=:versionTimeStamp where FilePath = :path  COLLATE NOCASE ";
+                            "update FileState set FileStatus= :fileStatus, ObjectID=:objectID, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where FilePath = :path  COLLATE NOCASE ";
                     else
                     {
                         command.CommandText =
-                            "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,ShortHash,FileStatus,OverlayStatus,ObjectID) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:shortHash,:fileStatus,:overlayStatus,:objectID)";
+                            "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,ETag,LastMD5,FileStatus,OverlayStatus,ObjectID) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:etag,:etag,:fileStatus,:overlayStatus,:objectID)";
                         command.Parameters.AddWithValue("id", Guid.NewGuid());
                     }
 
                     command.Parameters.AddWithValue("path", path);
                     command.Parameters.AddWithValue("checksum", objectInfo.X_Object_Hash);
-                    command.Parameters.AddWithValue("shortHash", objectInfo.ETag);
+                    command.Parameters.AddWithValue("etag", objectInfo.ETag);
                     command.Parameters.AddWithValue("version", objectInfo.Version);
                     command.Parameters.AddWithValue("versionTimeStamp", objectInfo.VersionTimestamp);
                     command.Parameters.AddWithValue("fileStatus", FileStatus.Unchanged);
@@ -877,7 +877,7 @@ namespace Pithos.Core.Agents
             }
         }
 
-        public void UpdateFileChecksum(string path, string shortHash, string checksum)
+        public void UpdateFileChecksum(string path, string etag, string checksum)
         {
             if (String.IsNullOrWhiteSpace(path))
                 throw new ArgumentNullException("path");
@@ -885,7 +885,18 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("The path must be rooted", "path");            
             Contract.EndContractBlock();
 
-            _persistenceAgent.Post(() => FileState.UpdateChecksum(path, shortHash,checksum));
+            _persistenceAgent.Post(() => FileState.UpdateChecksum(path, etag,checksum));
+        }
+
+        public void UpdateLastMD5(FileInfo file, string etag)
+        {
+            if (file==null)
+                throw new ArgumentNullException("file");
+            if (String.IsNullOrWhiteSpace(etag))
+                throw new ArgumentNullException("etag");
+            Contract.EndContractBlock();
+
+            _persistenceAgent.Post(() => FileState.UpdateLastMD5(file, etag));
         }
 
 
index b8e1ec7..d5207a4 100644 (file)
@@ -47,13 +47,15 @@ namespace Pithos.Core.Agents
 \r
                     var fileInfo = action.LocalFile;\r
 \r
+                    var progress=new Progress<double>(d=>\r
+                        StatusNotification.Notify(new StatusNotification(String.Format("Merkle Hashing for Upload {0:p} of {1}",d,fileInfo.Name))));\r
 \r
                     TreeHash localTreeHash;\r
-                    using (StatusNotification.GetNotifier("Hashing for Upload {0}", "Hashed for Upload {0}", fileInfo.Name))\r
+                    using (StatusNotification.GetNotifier("Merkle Hashing for Upload {0}", "Merkle Hashed for Upload {0}", fileInfo.Name))\r
                     {\r
                         localTreeHash = Signature.CalculateTreeHashAsync(fileInfo.FullName,\r
                                                                          action.AccountInfo.BlockSize,\r
-                                                                         action.AccountInfo.BlockHash, 1);\r
+                                                                         action.AccountInfo.BlockHash, 1,progress);\r
                     }\r
 \r
                     if (fileInfo.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase))\r
index 92795ba..d84c1f9 100644 (file)
@@ -115,11 +115,16 @@ namespace Pithos.Core
         /// </summary>
         /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
         [Property(NotNull = true, Default = "")]
-        public string ShortHash { get; set; }
+        public string ETag { get; set; }
+
+        [Property(NotNull = true, Default = "")]
+        public string LastMD5 { get; set; }
 
         [Property]
-        public DateTime? ShortHashDate { get; set; }
-        
+        public DateTime? LastWriteDate { get; set; }
+
+        [Property]
+        public long? LastLength { get; set; }
         
         [Property]
         public long? Version { get; set; }
@@ -259,7 +264,7 @@ namespace Pithos.Core
                                                        FilePath = absolutePath,
                                                        Id = Guid.NewGuid(),
                                                        OverlayStatus = newStatus,
-                                                       ShortHash = String.Empty,
+                                                       ETag = String.Empty,
                                                        IsFolder=Directory.Exists(absolutePath)
                                                    };
                                 newState.CreateAndFlush();
@@ -269,7 +274,7 @@ namespace Pithos.Core
 
         }
 */
-        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string shortHash)
+        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string etag)
         {
             if (string.IsNullOrWhiteSpace(absolutePath))
                 throw new ArgumentNullException("absolutePath");
@@ -290,7 +295,7 @@ namespace Pithos.Core
                                                        FilePath = absolutePath,
                                                        Id = Guid.NewGuid(),
                                                        OverlayStatus = newStatus,
-                                                       ShortHash = shortHash??String.Empty,
+                                                       ETag = etag??String.Empty,
                                                        IsFolder=Directory.Exists(absolutePath)
                                                    };
                                 newState.CreateAndFlush();
@@ -377,7 +382,7 @@ namespace Pithos.Core
             }, null);
         }*/
 
-        public static void UpdateChecksum(string absolutePath, string shortHash, string checksum)
+        public static void UpdateChecksum(string absolutePath, string etag, string checksum)
         {
             if (string.IsNullOrWhiteSpace(absolutePath))
                 throw new ArgumentNullException("absolutePath");
@@ -385,17 +390,55 @@ namespace Pithos.Core
 
             ExecuteWithRetry((session, instance) =>
                         {
-                            const string hqlUpdate = "update FileState set Checksum= :checksum,ShortHash=:shortHash where FilePath = :path ";
+                            const string hqlUpdate = "update FileState set Checksum= :checksum,ETag=:etag where FilePath = :path ";
                             var updatedEntities = session.CreateQuery(hqlUpdate)
                                 .SetString("path", absolutePath)
                                 .SetString("checksum", checksum)
-                                .SetString("shortHash", shortHash)
+                                .SetString("etag", etag)
                                 .ExecuteUpdate();
                             return updatedEntities;
                         }, null);
 
         }
 
+        public static void UpdateLastMD5(FileInfo file, string md5)
+        {
+            if (file==null)
+                throw new ArgumentNullException("file");
+            Contract.EndContractBlock();
+
+            ExecuteWithRetry((session, instance) =>
+                        {
+                            const string hqlUpdate = "update FileState set LastMD5=:md5, LastWriteDate=:date,LastLength=:length where FilePath = :path ";
+                            var fullName = file.WithProperCapitalization().FullName;
+
+                            var updatedEntities = session.CreateQuery(hqlUpdate)
+                                .SetDateTime("date", file.LastWriteTime)
+                                .SetInt64("length", file.Length)
+                                .SetString("md5", md5)
+                                .SetString("path", fullName)
+                                .ExecuteUpdate();
+                            if (updatedEntities == 0)
+                            {
+                                var newState = new FileState
+                                {
+                                    FilePath = fullName,
+                                    Id = Guid.NewGuid(),
+                                    OverlayStatus = FileOverlayStatus.Normal,
+                                    FileStatus=FileStatus.Unchanged,
+                                    IsFolder = false,
+                                    LastLength=file.Length,
+                                    LastWriteDate=file.LastWriteTime,
+                                    LastMD5 = md5 ?? String.Empty,
+                                    ETag=String.Empty
+                                };
+                                newState.CreateAndFlush();
+                            }
+                            return updatedEntities;
+                        }, null);
+
+        }
+
         public static void ChangeRootPath(string oldPath, string newPath)
         {
             if (String.IsNullOrWhiteSpace(oldPath))
@@ -438,18 +481,18 @@ namespace Pithos.Core
                     FilePath = info.FullName,
                     OverlayStatus = FileOverlayStatus.Unversioned,
                     FileStatus = FileStatus.Created,
-                    ShortHash=String.Empty,
+                    ETag=String.Empty,
                     Id = Guid.NewGuid()
                 };
 
             
-            var shortHash = ((FileInfo)info).ComputeShortHash(notification);
+            var etag = ((FileInfo)info).ComputeShortHash(notification);
             var fileState = new FileState
                                 {
                                     FilePath = info.FullName,
                                     OverlayStatus = FileOverlayStatus.Unversioned,
                                     FileStatus = FileStatus.Created,               
-                                    ShortHash=shortHash,
+                                    ETag=etag,
                                     Id = Guid.NewGuid()
                                 };
             return fileState;
index 2711ab8..6b8c6be 100644 (file)
@@ -53,8 +53,8 @@ namespace Pithos.Core
     [ContractClass(typeof(IStatusKeeperContract))]
     public interface IStatusKeeper
     {
-        Task SetFileOverlayStatus(string path, FileOverlayStatus status, string shortHash = null);
-        void UpdateFileChecksum(string path, string shortHash, string checksum);
+        Task SetFileOverlayStatus(string path, FileOverlayStatus status, string etag = null);
+        void UpdateFileChecksum(string path, string etag, string checksum);
         void SetFileStatus(string path, FileStatus status);
         FileStatus GetFileStatus(string path);
         void ClearFileStatus(string path);
@@ -80,19 +80,20 @@ namespace Pithos.Core
 
         void CleanupStaleStates(Network.AccountInfo accountInfo, List<ObjectInfo> objectInfos);
         void CleanupOrphanStates();
+        void UpdateLastMD5(FileInfo path, string etag);
     }
 
     [ContractClassFor(typeof(IStatusKeeper))]
     public abstract class IStatusKeeperContract : IStatusKeeper
     {
-        public Task SetFileOverlayStatus(string path, FileOverlayStatus status, string shortHash = null)
+        public Task SetFileOverlayStatus(string path, FileOverlayStatus status, string etag = null)
         {
             Contract.Requires(!String.IsNullOrWhiteSpace(path));
             Contract.Requires(Path.IsPathRooted(path));
             return default(Task);
         }
 
-        public void UpdateFileChecksum(string path, string shortHash, string checksum)
+        public void UpdateFileChecksum(string path, string etag, string checksum)
         {
             Contract.Requires(!String.IsNullOrWhiteSpace(path));
             Contract.Requires(checksum!=null);
@@ -255,5 +256,10 @@ namespace Pithos.Core
         public void CleanupOrphanStates()
         {            
         }
+
+        public void UpdateLastMD5(FileInfo path, string etag)
+        {
+            
+        }
     }
 }
index 4c7ef36..6462aa8 100644 (file)
@@ -69,7 +69,7 @@ namespace Pithos.Core
 
         public string Hash { get; set; }        
         public string LastUpdateHash { get; set; }
-        public string ShortHash { get; set; }
+        public string ETag { get; set; }
 
 /*
         public WorkflowState(AccountInfo accountInfo)
@@ -96,7 +96,7 @@ namespace Pithos.Core
             Path = state.FilePath.ToLower();
             FileName = System.IO.Path.GetFileName(state.FilePath).ToLower();
             Hash = state.Checksum;
-            ShortHash = state.ShortHash;
+            ETag = state.ETag;
             Status = state.OverlayStatus == FileOverlayStatus.Unversioned
                          ? FileStatus.Created
                          : state.FileStatus;
index aa402d0..1cc0030 100644 (file)
@@ -24,7 +24,7 @@ namespace Pithos.Network.Test
                              };
             client.Authenticate();
             var fileName = @"vlc-1.1.11-win32.exe";
-            var treeHash=Signature.CalculateTreeHashAsync(Path.Combine(@"e:\pithos\" ,fileName), 4*1024*1024 , "sha256", 2);
+            var treeHash = Signature.CalculateTreeHashAsync(Path.Combine(@"e:\pithos\", fileName), 4 * 1024 * 1024, "sha256", 2, new Progress<double>());
             var result = client.PutHashMap(account, "pithos", fileName, treeHash).Result;
 
             Assert.AreEqual(0,result.Count);
index 187b5c1..7c3a8cc 100644 (file)
@@ -35,9 +35,9 @@ namespace Pithos.Core.Test
             var fileSize = new FileInfo(file).Length;
             var numBlocks = decimal.Ceiling(fileSize/blockSize);
 
-            var md5 = Signature.CalculateMD5(file);            
+            var md5 = Signature.CalculateMD5(file);
 
-            var hash1 = Signature.CalculateTreeHashAsync(file, (int) blockSize,"sha256", 2);
+            var hash1 = Signature.CalculateTreeHashAsync(file, (int)blockSize, "sha256", 2, new Progress<double>());
             Assert.IsNotNull(hash1.Hashes);
             Assert.AreEqual(numBlocks, hash1.Hashes.Count());
 
@@ -62,7 +62,7 @@ namespace Pithos.Core.Test
 
             var md5 = Signature.CalculateMD5(file);
 
-            var hash1 = Signature.CalculateTreeHashAsync(file, (int) blockSize, "sha256", 2);
+            var hash1 = Signature.CalculateTreeHashAsync(file, (int)blockSize, "sha256", 2, new Progress<double>());
             hash1.FileId = Guid.NewGuid();
             var task = hash1.Save(@"e:\")
                 .ContinueWith(_ => TreeHash.LoadTreeHash(@"e:\", hash1.FileId)).Unwrap();            
@@ -90,20 +90,20 @@ namespace Pithos.Core.Test
             decimal blockSize = 4 * 1048576;
 
             Trace.WriteLine("1");
-            var stopwatch = Stopwatch.StartNew();            
-            var hash1 = Signature.CalculateTreeHashAsync(file, (int)blockSize, "sha256", 1);
+            var stopwatch = Stopwatch.StartNew();
+            var hash1 = Signature.CalculateTreeHashAsync(file, (int)blockSize, "sha256", 1, new Progress<double>());
             stopwatch.Stop();
             Trace.WriteLine(stopwatch.Elapsed);
             
             Trace.WriteLine("2");
-            stopwatch.Restart();            
-            var hash2 = Signature.CalculateTreeHashAsync(file, (int)blockSize, "sha256", 2);
+            stopwatch.Restart();
+            var hash2 = Signature.CalculateTreeHashAsync(file, (int)blockSize, "sha256", 2, new Progress<double>());
             stopwatch.Stop();
             Trace.WriteLine(stopwatch.Elapsed);
 
             Trace.WriteLine("3");
             stopwatch.Restart();
-            var hash3 = Signature.CalculateTreeHashAsync(file, (int)blockSize, "sha256", 3);
+            var hash3 = Signature.CalculateTreeHashAsync(file, (int)blockSize, "sha256", 3, new Progress<double>());
             stopwatch.Stop();
             Trace.WriteLine(stopwatch.Elapsed);
 
@@ -148,7 +148,7 @@ namespace Pithos.Core.Test
             client.UsePithos = true;            
             client.Authenticate();
             var fileName = @"vlc-1.1.11-win32.exe";
-            var localHash= Signature.CalculateTreeHashAsync(Path.Combine(@"e:\pithos\", fileName), 4 * 1024 * 1024, "sha256", 2);
+            var localHash= Signature.CalculateTreeHashAsync(Path.Combine(@"e:\pithos\", fileName), 4 * 1024 * 1024, "sha256", 2,new Progress<double>());
             var upHash= client.GetHashMap(fileName, account, "pithos").Result;
 
             Assert.AreEqual(upHash.TopHash, localHash.TopHash);
@@ -161,8 +161,8 @@ namespace Pithos.Core.Test
         {
 
             var fileName = @"vlc-1.1.11-win32.exe";
-            var syncHash= Signature.CalculateTreeHash(Path.Combine(@"e:\pithos\", fileName), 4 * 1024 * 1024, "sha256");
-            var asyncHash = Signature.CalculateTreeHashAsync(Path.Combine(@"e:\pithos\", fileName), 4 * 1024 * 1024, "sha256", 2)
+            var syncHash= Signature.CalculateTreeHash(Path.Combine(@"e:\pithos\", fileName), 4 * 1024 * 1024, "sha256",new Progress<double>());
+            var asyncHash = Signature.CalculateTreeHashAsync(Path.Combine(@"e:\pithos\", fileName), 4 * 1024 * 1024, "sha256", 2, new Progress<double>())
                 ;
 
             Assert.AreEqual(syncHash.TopHash, asyncHash.TopHash);
index a157682..84f582f 100644 (file)
@@ -230,7 +230,7 @@ namespace Pithos.Network
             return _bufferMgr;\r
         }\r
 \r
-        public static async Task<ConcurrentDictionary<long, byte[]>> CalculateBlockHashesInPlacePFor(FileStream stream, int blockSize, string algorithm, int parallelism)\r
+        public static async Task<ConcurrentDictionary<long, byte[]>> CalculateBlockHashesInPlacePFor(FileStream stream, int blockSize, string algorithm, int parallelism,IProgress<double> progress )\r
         {\r
             if (stream == null)\r
                 throw new ArgumentNullException("stream");\r
@@ -302,6 +302,7 @@ namespace Pithos.Network
                                                                                 (double)filePosition / size);\r
                             */\r
                                                             hashes[filePosition] = hash;\r
+                                                            progress.Report((long)hashes.Count*blockSize*1.0/stream.Length);\r
                                                         });\r
                     }\r
                     bufIdx = (bufIdx + 1)%parallelism;\r
index a741269..7016d02 100644 (file)
@@ -56,7 +56,10 @@ namespace Pithos.Network
     {
         private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
 
-        public static string CalculateMD5(FileInfo info)
+        public const string MD5_EMPTY = "d41d8cd98f00b204e9800998ecf8427e";
+
+
+        public static string CalculateMD5(FileSystemInfo info)
         {
             if (info==null)
                 throw new ArgumentNullException("info");
@@ -64,6 +67,9 @@ namespace Pithos.Network
                 throw new ArgumentException("info.FullName is empty","info");
             Contract.EndContractBlock();
 
+            if (info is DirectoryInfo)
+                return MD5_EMPTY;
+
             return CalculateMD5(info.FullName);
         }
 
@@ -115,7 +121,7 @@ namespace Pithos.Network
             return shb.ToString().ToLower();
         }
 
-        public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, int blockSize, string algorithm)
+        public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, int blockSize, string algorithm,IProgress<double> progress )
         {
             if (fileInfo == null)
                 throw new ArgumentNullException("fileInfo");
@@ -130,7 +136,7 @@ namespace Pithos.Network
             if (fileInfo is DirectoryInfo || !fileInfo.Exists)
                 return TreeHash.Empty;
 
-            return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm);
+            return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm,progress);
         }
 
         /// <summary>
@@ -140,7 +146,7 @@ namespace Pithos.Network
         /// <param name="blockSize">Block size used to calculate leaf hashes</param>
         /// <param name="algorithm"></param>
         /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
-        public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm)
+        public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm,IProgress<double> progress )
         {
             if (String.IsNullOrWhiteSpace(filePath))
                 throw new ArgumentNullException("filePath");
@@ -149,11 +155,11 @@ namespace Pithos.Network
             if (String.IsNullOrWhiteSpace(algorithm))
                 throw new ArgumentNullException("algorithm");
             Contract.EndContractBlock();           
-            var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 1);
+            var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 1,progress);
             return hash;
         }
         
-        public static TreeHash CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism)
+        public static TreeHash CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism,IProgress<double> progress )
         {
             if (fileInfo == null)
                 throw new ArgumentNullException("fileInfo");
@@ -165,11 +171,11 @@ namespace Pithos.Network
                 throw new ArgumentNullException("algorithm");
             Contract.EndContractBlock();
             
-            return CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism);
+            return CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism,progress);
         }
 
 
-        public static TreeHash CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism)
+        public static TreeHash CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism,IProgress<double> progress )
         {
             if (String.IsNullOrWhiteSpace(filePath))
                 throw new ArgumentNullException("filePath");
@@ -193,7 +199,7 @@ namespace Pithos.Network
             using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
             {
                 //Calculate the blocks asyncrhonously
-                var hashes = BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism).Result;                
+                var hashes = BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism,progress).Result;                
 
                 //And then proceed with creating and returning a TreeHash
                 var length = stream.Length;