Multiple changes to enable delete detection, safer uploading
[pithos-ms-client] / trunk / Pithos.Core / Agents / BlockUpdater.cs
index f11f4e4..6b0d310 100644 (file)
@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Diagnostics.Contracts;
 using System.IO;
 using System.Linq;
+using System.Security.Cryptography;
 using System.Text;
 using System.Threading.Tasks;
 using Pithos.Network;
@@ -20,33 +21,81 @@ namespace Pithos.Core.Agents
         public TreeHash ServerHash { get; private set; }
 
         public string TempPath { get; private set; }
-        
+
 
         public BlockUpdater(string fragmentsPath, string filePath, string relativePath,TreeHash serverHash)
-        {            
+        {   
+            if (String.IsNullOrWhiteSpace(fragmentsPath))
+                throw new ArgumentNullException("fragmentsPath");
+            if (!Path.IsPathRooted(fragmentsPath))
+                throw new ArgumentException("The fragmentsPath must be rooted", "fragmentsPath");
+            
+            if (string.IsNullOrWhiteSpace(filePath))
+                throw new ArgumentNullException("filePath");
+            if (!Path.IsPathRooted(filePath))
+                throw new ArgumentException("The filePath must be rooted", "filePath");
+            
+            if (string.IsNullOrWhiteSpace(relativePath))
+                throw new ArgumentNullException("relativePath");
+            if (Path.IsPathRooted(relativePath))
+                throw new ArgumentException("The relativePath must NOT be rooted", "relativePath");
+
+            if (serverHash == null)
+                throw new ArgumentNullException("serverHash");
+            Contract.EndContractBlock();
+
             FragmentsPath=fragmentsPath;
             FilePath = filePath;
             RelativePath=relativePath;
             ServerHash = serverHash;
-
-            Start();
-        }
-
-        public void Start()
-        {
             //The file will be stored in a temporary location while downloading with an extension .download
             TempPath = Path.Combine(FragmentsPath, RelativePath + ".download");
+
             var directoryPath = Path.GetDirectoryName(TempPath);
             if (!Directory.Exists(directoryPath))
                 Directory.CreateDirectory(directoryPath);
+
+            LoadOrphans(directoryPath);
+        }
+
+        private void LoadOrphans(string directoryPath)
+        {
+            if (string.IsNullOrWhiteSpace(directoryPath))
+                throw new ArgumentNullException("directoryPath");
+            if (!Path.IsPathRooted(directoryPath))
+                throw new ArgumentException("The directoryPath must be rooted", "directoryPath");
+            Contract.EndContractBlock();
+
+            var fileNamename = Path.GetFileName(FilePath);
+            var orphans = Directory.GetFiles(directoryPath, fileNamename + ".*");
+            foreach (var orphan in orphans)
+            {
+                using (HashAlgorithm hasher = HashAlgorithm.Create(ServerHash.BlockHash))
+                {
+                    var buffer=File.ReadAllBytes(orphan);
+                    //The server truncates nulls before calculating hashes, have to do the same
+                    //Find the last non-null byte, starting from the end
+                    var lastByteIndex = Array.FindLastIndex(buffer, buffer.Length-1, aByte => aByte != 0);
+                    var binHash = hasher.ComputeHash(buffer,0,lastByteIndex);
+                    var hash = binHash.ToHashString();
+                    _orphanBlocks[hash] = orphan;
+                }
+            }
         }
 
 
         public void Commit()
-        {                       
+        {
+            if (String.IsNullOrWhiteSpace(FilePath))
+                throw new InvalidOperationException("FilePath is empty");
+            if (String.IsNullOrWhiteSpace(TempPath))
+                throw new InvalidOperationException("TempPath is empty");
+            Contract.EndContractBlock();
+
             //Copy the file to a temporary location. Changes will be made to the
             //temporary file, then it will replace the original file
-            File.Copy(FilePath, TempPath, true);
+            if (File.Exists(FilePath))
+                File.Copy(FilePath, TempPath, true);
 
             //Set the size of the file to the size specified in the treehash
             //This will also create an empty file if the file doesn't exist            
@@ -74,6 +123,12 @@ namespace Pithos.Core.Agents
 
         private void SwapFiles()
         {
+            if (String.IsNullOrWhiteSpace(FilePath))
+                throw new InvalidOperationException("FilePath is empty");
+            if (String.IsNullOrWhiteSpace(TempPath))
+                throw new InvalidOperationException("TempPath is empty");            
+            Contract.EndContractBlock();
+
             if (File.Exists(FilePath))
                 File.Replace(TempPath, FilePath, null, true);
             else
@@ -82,13 +137,17 @@ namespace Pithos.Core.Agents
 
         private void ClearBlocks()
         {
-            foreach (var block in _blocks)
+            //Get all the the block paths, orphan or not
+            var paths= _blocks.Select(pair => pair.Value)
+                          .Union(_orphanBlocks.Select(pair => pair.Value));
+            foreach (var filePath in paths)
             {
-                var filePath = block.Value;                
                 File.Delete(filePath);
             }
+
             File.Delete(TempPath);
             _blocks.Clear();
+            _orphanBlocks.Clear();
         }
 
         //Change the file's size, possibly truncating or adding to it
@@ -128,12 +187,27 @@ namespace Pithos.Core.Agents
         }*/
 
         ConcurrentDictionary<int,string> _blocks=new ConcurrentDictionary<int, string>();
+        ConcurrentDictionary<string, string> _orphanBlocks = new ConcurrentDictionary<string, string>();
+
+        public bool UseOrphan(int blockIndex, string blockHash)
+        {
+            string blockPath=null;
+            if (_orphanBlocks.TryGetValue(blockHash,out blockPath))
+            {
+                _blocks[blockIndex] = blockPath;
+                return true;
+            }
+            return false;
+        }
 
         public Task StoreBlock(int blockIndex,byte[] buffer)
         {
-            var blockPath = String.Format("{0}.{1:3}", TempPath, blockIndex);
+            var blockPath = String.Format("{0}.{1:000000}", TempPath, blockIndex);
             _blocks[blockIndex] = blockPath;
-            
+            //Remove any orphan files
+            if (File.Exists(blockPath))
+                File.Delete(blockPath);
+
             return FileAsync.WriteAllBytes(blockPath, buffer);
         }