Replaced Merkle hash with MD5 for change checking
[pithos-ms-client] / trunk / Pithos.Core / FileState.cs
index 5e05c35..3995cac 100644 (file)
 #endregion
 using System.Diagnostics.Contracts;
 using System.IO;
+using System.Reflection;
 using System.Threading.Tasks;
 using Castle.ActiveRecord;
 using Castle.ActiveRecord.Framework;
+using Castle.ActiveRecord.Queries;
+using NHibernate.Criterion;
 using Pithos.Core.Agents;
 using Pithos.Interfaces;
 using Pithos.Network;
@@ -52,8 +55,7 @@ using log4net;
 namespace Pithos.Core
 {
     using System;
-    using System.Collections.Generic;
-    using System.Linq;
+    using System.Collections.Generic;    
 
     /// <summary>
     /// TODO: Update summary.
@@ -61,7 +63,8 @@ namespace Pithos.Core
     [ActiveRecord]
     public class FileState : ActiveRecordLinqBase<FileState>
     {
-        private static readonly ILog Log = LogManager.GetLogger("FileState");
+        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
 
         private IList<FileTag> _tags = new List<FileTag>();
 
@@ -69,6 +72,10 @@ namespace Pithos.Core
         public Guid Id { get; set; }
 
         
+        //[Property(Unique = true, UniqueKey = "IX_FileState_ObjectID")]
+        [Property]
+        public string ObjectID { get; set; }
+
         [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")]
         public string FilePath { get; set; }
 
@@ -79,7 +86,48 @@ namespace Pithos.Core
         public FileStatus FileStatus { get; set; }
 
         [Property]
-        public string Checksum { get; set; }
+        public string ConflictReason { get; set; }
+
+        private string _checksum;
+
+        /// <summary>
+        /// The tophash value of the file, calculated by using a Merkle hash with the SHA256 algorithm
+        /// </summary>
+        /// <remarks>
+        /// The SHA256 algorithm is substantially more expenive than other algorithms.
+        /// Recalculating the Checksum should be avoided whenever possible.
+        /// </remarks>
+        [Property]
+        public string Checksum
+        {
+            get
+            {
+                return _checksum;
+            }
+            set
+            {
+                _checksum = value;
+            }
+        }
+
+        private string _shortHash;
+
+        /// <summary>
+        /// An easy to calcualte hash over the entire file, used to detect file changes
+        /// </summary>
+        /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
+        [Property(NotNull=true,Default="")]
+        public string ShortHash
+        {
+            get
+            {
+                return _shortHash;
+            }
+            set
+            {
+                _shortHash = value;
+            }
+        }
 
 
         [Property]
@@ -199,7 +247,38 @@ namespace Pithos.Core
 
         }
 
-        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
+        /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
+        {
+            if (string.IsNullOrWhiteSpace(absolutePath))
+                throw new ArgumentNullException("absolutePath");
+            Contract.EndContractBlock();
+
+            ExecuteWithRetry((session, instance) =>
+                        {
+                            const string hqlUpdate =
+                                "update FileState set OverlayStatus= :status where FilePath = :path ";
+                            var updatedEntities = session.CreateQuery(hqlUpdate)                                
+                                .SetString("path", absolutePath)
+                                .SetEnum("status", newStatus)                                
+                                .ExecuteUpdate();
+                            if (updatedEntities == 0)
+                            {
+                                var newState = new FileState
+                                                   {
+                                                       FilePath = absolutePath,
+                                                       Id = Guid.NewGuid(),
+                                                       OverlayStatus = newStatus,
+                                                       ShortHash = String.Empty,
+                                                       IsFolder=Directory.Exists(absolutePath)
+                                                   };
+                                newState.CreateAndFlush();
+                            }
+                            return null;
+                        }, null);
+
+        }
+*/
+        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string shortHash)
         {
             if (string.IsNullOrWhiteSpace(absolutePath))
                 throw new ArgumentNullException("absolutePath");
@@ -220,6 +299,7 @@ namespace Pithos.Core
                                                        FilePath = absolutePath,
                                                        Id = Guid.NewGuid(),
                                                        OverlayStatus = newStatus,
+                                                       ShortHash = shortHash??String.Empty,
                                                        IsFolder=Directory.Exists(absolutePath)
                                                    };
                                 newState.CreateAndFlush();
@@ -306,7 +386,7 @@ namespace Pithos.Core
             }, null);
         }*/
 
-        public static void UpdateChecksum(string absolutePath, string checksum)
+        public static void UpdateChecksum(string absolutePath, string shortHash, string checksum)
         {
             if (string.IsNullOrWhiteSpace(absolutePath))
                 throw new ArgumentNullException("absolutePath");
@@ -314,10 +394,11 @@ namespace Pithos.Core
 
             ExecuteWithRetry((session, instance) =>
                         {
-                            const string hqlUpdate = "update FileState set Checksum= :checksum where FilePath = :path ";
+                            const string hqlUpdate = "update FileState set Checksum= :checksum,ShortHash=:shortHash where FilePath = :path ";
                             var updatedEntities = session.CreateQuery(hqlUpdate)
                                 .SetString("path", absolutePath)
                                 .SetString("checksum", checksum)
+                                .SetString("shortHash", shortHash)
                                 .ExecuteUpdate();
                             return updatedEntities;
                         }, null);
@@ -354,49 +435,35 @@ namespace Pithos.Core
                             }, null);
         }
 
-        public static Task<FileState> CreateForAsync(string filePath, int blockSize, string algorithm)
+        public static FileState CreateFor(FileSystemInfo info,IStatusNotification notification)
         {
-            if (blockSize <= 0)
-                throw new ArgumentOutOfRangeException("blockSize");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
+            if(info==null)
+                throw new ArgumentNullException("info");
             Contract.EndContractBlock();
-
-
+            
+            if (info is DirectoryInfo)
+                return new FileState
+                {
+                    FilePath = info.FullName,
+                    OverlayStatus = FileOverlayStatus.Unversioned,
+                    FileStatus = FileStatus.Created,
+                    ShortHash=String.Empty,
+                    Id = Guid.NewGuid()
+                };
+
+            
+            var shortHash = ((FileInfo)info).ComputeShortHash(notification);
             var fileState = new FileState
                                 {
-                                    FilePath = filePath,
+                                    FilePath = info.FullName,
                                     OverlayStatus = FileOverlayStatus.Unversioned,
-                                    FileStatus = FileStatus.Created,
+                                    FileStatus = FileStatus.Created,               
+                                    ShortHash=shortHash,
                                     Id = Guid.NewGuid()
                                 };
-
-
-            return fileState.UpdateHashesAsync(blockSize, algorithm);
+            return fileState;
         }
 
-        public async Task<FileState> UpdateHashesAsync(int blockSize, string algorithm)
-        {
-            if (blockSize <= 0)
-                throw new ArgumentOutOfRangeException("blockSize");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
-            Contract.EndContractBlock();
-
-            //Skip updating the hash for folders
-            if (Directory.Exists(FilePath))
-                return this;
-
-            var hash = await TaskEx.Run(() =>
-                                            {
-                                                var info = new FileInfo(FilePath);
-                                                return info.CalculateHash(blockSize, algorithm);
-                                            });
-
-            Checksum = hash;
-
-            return this;
-        }
 
         private static void ExecuteWithRetry(NHibernateDelegate call, object state)
         {
@@ -417,6 +484,48 @@ namespace Pithos.Core
                         throw;
                 }
         }
+
+        /// <summary>
+        /// Mark Unversioned all FileState rows from the database whose path
+        /// starts with one of the removed paths
+        /// </summary>
+        /// <param name="removed"></param>
+        public static void UnversionPaths(List<string> removed)
+        {
+            if (removed == null)
+                return;
+            if (removed.Count == 0)
+                return;
+
+            //Create a disjunction (list of OR statements
+            var disjunction = new Disjunction();            
+            foreach (var path in removed)
+            {
+                //with the restriction FileState.FilePath like '@path%'
+                disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)
+                    .IsLike(path, MatchMode.Start));
+            }
+
+            //Generate a query from the disjunction
+            var query=QueryOver.Of<FileState>().Where(disjunction);
+                        
+            ExecuteWithRetry((session,instance)=>
+                                 {
+                                     using (var t=session.BeginTransaction())
+                                     {
+                                         var states = query.GetExecutableQueryOver(session).List();
+                                         foreach (var state in states)
+                                         {
+                                             state.FileStatus = FileStatus.Unversioned;
+                                             state.OverlayStatus = FileOverlayStatus.Unversioned;
+                                             state.Update();
+                                         }
+                                         t.Commit();
+                                     }
+                                     return null;
+                                 },null);
+        }
+
     }
 
     [ActiveRecord("Tags")]