Convert ActiveRecord update code to direct ADO calls to reduce locks
[pithos-ms-client] / trunk / Pithos.Core / FileState.cs
index 8d04ba3..e523cc7 100644 (file)
@@ -1,39 +1,71 @@
 // -----------------------------------------------------------------------
-// <copyright file="FileState.cs" company="Microsoft">
-// TODO: Update copyright text.
+// <copyright file="FileState.cs" company="GRNet">
+// Copyright 2011 GRNET S.A. All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or
+// without modification, are permitted provided that the following
+// conditions are met:
+// 
+//   1. Redistributions of source code must retain the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer in the documentation and/or other materials
+//      provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+// 
+// The views and conclusions contained in the software and
+// documentation are those of the authors and should not be
+// interpreted as representing official policies, either expressed
+// or implied, of GRNET S.A.
 // </copyright>
 // -----------------------------------------------------------------------
 
+using System.Data.SQLite;
 using System.Diagnostics.Contracts;
 using System.IO;
 using System.Threading.Tasks;
 using Castle.ActiveRecord;
 using Castle.ActiveRecord.Framework;
-using NHibernate.Engine;
 using Pithos.Core.Agents;
 using Pithos.Interfaces;
-using Pithos.Network;
+using log4net;
 
 namespace Pithos.Core
 {
     using System;
     using System.Collections.Generic;
     using System.Linq;
-    using System.Text;
 
     /// <summary>
     /// TODO: Update summary.
     /// </summary>
     [ActiveRecord]
-    public class FileState:ActiveRecordLinqBase<FileState>
+    public class FileState : ActiveRecordLinqBase<FileState>
     {
+        private static readonly ILog Log = LogManager.GetLogger("FileState");
+
         private string _filePath;
-        private IList<FileTag> _tags=new List<FileTag>();
+        private IList<FileTag> _tags = new List<FileTag>();
 
         [PrimaryKey(PrimaryKeyType.Guid)]
         public Guid Id { get; set; }
 
-        [Property(Unique=true,UniqueKey="IX_FileState_FilePath")]
+        [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")]
         public string FilePath
         {
             get { return _filePath; }
@@ -49,52 +81,253 @@ namespace Pithos.Core
         [Property]
         public string Checksum { get; set; }
 
-/*
-        [Property]
-        public string TopHash { get; set; }
-*/
 
         [Property]
         public long? Version { get; set; }
 
         [Property]
         public DateTime? VersionTimeStamp { get; set; }
-        
 
-       [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true,Inverse=true)]
+
+        [Property]
+        public bool IsShared { get; set; }
+
+        [Property]
+        public string SharedBy { get; set; }
+
+        [Property]
+        public bool ShareWrite { get; set; }
+
+
+        [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)]
         public IList<FileTag> Tags
         {
-            get { return _tags; }   
-            set { _tags=value;}
+            get { return _tags; }
+            set { _tags = value; }
         }
 
-//        [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true)]
-//        public IList<FileHash> Hashes { get; set; }
 
-//        [Property]
-//        public byte[] HashmapHash { get; set; }
-       
         public static FileState FindByFilePath(string absolutePath)
         {
             if (string.IsNullOrWhiteSpace(absolutePath))
                 throw new ArgumentNullException("absolutePath");
             Contract.EndContractBlock();
-            return Queryable.FirstOrDefault(s => s.FilePath == absolutePath.ToLower());
+            try
+            {
+
+                
+
+                return Queryable.FirstOrDefault(s => s.FilePath == absolutePath.ToLower());
+            }
+            catch (Exception ex)
+            {
+                Log.Error(ex.ToString());
+                throw;
+            }
+
+
+        }
+
+       /* public static void DeleteByFilePath(string absolutePath)
+        {
+            if (string.IsNullOrWhiteSpace(absolutePath))
+                throw new ArgumentNullException("absolutePath");
+            Contract.EndContractBlock();
+
+            ExecuteWithRetry((session, instance) =>
+                        {
+                            const string hqlDelete = "delete FileState where FilePath = :path";
+                            var deletedEntities = session.CreateQuery(hqlDelete)
+                                .SetString("path", absolutePath.ToLower())
+                                .ExecuteUpdate();
+                            return deletedEntities;
+                        }, null);
+
+        }*/
+
+        public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
+        {
+            if (string.IsNullOrWhiteSpace(absolutePath))
+                throw new ArgumentNullException("absolutePath");
+            Contract.EndContractBlock();
+
+            ExecuteWithRetry((session, instance) =>
+                        {
+                            const string hqlUpdate = "update FileState set FileStatus= :status where FilePath = :path  ";
+                            var updatedEntities = session.CreateQuery(hqlUpdate)
+                                .SetString("path", absolutePath.ToLower())
+                                .SetEnum("status", newStatus)
+                                .ExecuteUpdate();
+                            if (updatedEntities == 0)
+                            {
+                                var newState = new FileState
+                                                   {
+                                                       FilePath = absolutePath.ToLower(),
+                                                       Id = Guid.NewGuid(),
+                                                       FileStatus = newStatus
+                                                   };
+                                newState.CreateAndFlush();
+                            }
+                            return null;
+                        }, null);
+
         }
 
-        public static void DeleteByFilePath(string absolutePath)
+        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
         {
-            if(string.IsNullOrWhiteSpace(absolutePath))
+            if (string.IsNullOrWhiteSpace(absolutePath))
                 throw new ArgumentNullException("absolutePath");
             Contract.EndContractBlock();
-            
-            var stateKeys = from state in FileState.Queryable
-                            where state.FilePath == absolutePath.ToLower()
-                            select state.Id;
-            DeleteAll(stateKeys);                                           
+
+            ExecuteWithRetry((session, instance) =>
+                        {
+                            const string hqlUpdate =
+                                "update FileState set OverlayStatus= :status where FilePath = :path  ";
+                            var updatedEntities = session.CreateQuery(hqlUpdate)
+                                .SetString("path", absolutePath.ToLower())
+                                .SetEnum("status", newStatus)
+                                .ExecuteUpdate();
+                            if (updatedEntities == 0)
+                            {
+                                var newState = new FileState
+                                                   {
+                                                       FilePath = absolutePath,
+                                                       Id = Guid.NewGuid(),
+                                                       OverlayStatus = newStatus
+                                                   };
+                                newState.CreateAndFlush();
+                            }
+                            return null;
+                        }, null);
+
         }
 
-        public static Task<FileState> CreateForAsync(string filePath,int blockSize,string algorithm)
+/*
+        public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
+        {
+            if (string.IsNullOrWhiteSpace(absolutePath))
+                throw new ArgumentNullException("absolutePath");
+            Contract.EndContractBlock();
+
+            ExecuteWithRetry((session, instance) =>
+                        {
+                            const string hqlUpdate =
+                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path  ";
+                            var updatedEntities = session.CreateQuery(hqlUpdate)
+                                .SetString("path", absolutePath.ToLower())
+                                .SetEnum("fileStatus", fileStatus)
+                                .SetEnum("overlayStatus", overlayStatus)
+                                .ExecuteUpdate();
+                            return updatedEntities;
+                        }, null);
+
+        }
+*/
+
+/*
+        public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
+        {
+            if (string.IsNullOrWhiteSpace(absolutePath))
+                throw new ArgumentNullException("absolutePath");
+            Contract.EndContractBlock();
+
+            ExecuteWithRetry((session, instance) =>
+                        {
+                            const string hqlUpdate =
+                                "update FileState set FileStatus= :fileStatus where FilePath = :path  ";
+                            var updatedEntities = session.CreateQuery(hqlUpdate)
+                                .SetString("path", absolutePath.ToLower())
+                                .SetEnum("fileStatus", fileStatus)
+                                .ExecuteUpdate();
+                            return updatedEntities;
+                        }, null);
+
+        }
+
+*/
+        public static void RenameState(string oldPath, string newPath)
+        {
+            if (string.IsNullOrWhiteSpace(oldPath))
+                throw new ArgumentNullException("oldPath");
+            Contract.EndContractBlock();
+
+            ExecuteWithRetry((session, instance) =>
+                        {
+                            const string hqlUpdate =
+                                "update FileState set FilePath= :newPath where FilePath = :oldPath  ";
+                            var updatedEntities = session.CreateQuery(hqlUpdate)
+                                .SetString("oldPath", oldPath.ToLower())
+                                .SetString("newPath", newPath.ToLower())
+                                .ExecuteUpdate();
+                            return updatedEntities;
+                        }, null);
+
+        }
+
+     /*   public static void UpdateStatus(Guid id, FileStatus fileStatus)
+        {
+
+            ExecuteWithRetry((session, instance) =>
+            {
+                const string hqlUpdate =
+                    "update FileState set FileStatus= :fileStatus where Id = :id  ";
+                var updatedEntities = session.CreateQuery(hqlUpdate)
+                    .SetGuid("id", id)
+                    .SetEnum("fileStatus", fileStatus)
+                    .ExecuteUpdate();
+                return updatedEntities;
+            }, null);
+        }*/
+
+        public static void UpdateChecksum(string absolutePath, string checksum)
+        {
+            if (string.IsNullOrWhiteSpace(absolutePath))
+                throw new ArgumentNullException("absolutePath");
+            Contract.EndContractBlock();
+
+            ExecuteWithRetry((session, instance) =>
+                        {
+                            const string hqlUpdate = "update FileState set Checksum= :checksum where FilePath = :path  ";
+                            var updatedEntities = session.CreateQuery(hqlUpdate)
+                                .SetString("path", absolutePath.ToLower())
+                                .SetString("checksum", checksum)
+                                .ExecuteUpdate();
+                            return updatedEntities;
+                        }, null);
+
+        }
+
+        public static void ChangeRootPath(string oldPath, string newPath)
+        {
+            if (String.IsNullOrWhiteSpace(oldPath))
+                throw new ArgumentNullException("oldPath");
+            if (!Path.IsPathRooted(oldPath))
+                throw new ArgumentException("oldPath must be an absolute path", "oldPath");
+            if (string.IsNullOrWhiteSpace(newPath))
+                throw new ArgumentNullException("newPath");
+            if (!Path.IsPathRooted(newPath))
+                throw new ArgumentException("newPath must be an absolute path", "newPath");
+            Contract.EndContractBlock();
+
+            //Ensure the paths end with the same character
+            if (!oldPath.EndsWith("\\"))
+                oldPath = oldPath + "\\";
+            if (!newPath.EndsWith("\\"))
+                newPath = newPath + "\\";
+
+                ExecuteWithRetry((session, instance) =>
+                            {
+                                const string hqlUpdate =
+                                    "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
+                                var renames = session.CreateQuery(hqlUpdate)
+                                    .SetString("oldPath", oldPath.ToLower())
+                                    .SetString("newPath", newPath.ToLower())
+                                    .ExecuteUpdate();
+                                return renames;
+                            }, null);
+        }
+
+        public static Task<FileState> CreateForAsync(string filePath, int blockSize, string algorithm)
         {
             if (blockSize <= 0)
                 throw new ArgumentOutOfRangeException("blockSize");
@@ -105,41 +338,61 @@ namespace Pithos.Core
 
             var fileState = new FileState
                                 {
-                                    FilePath = filePath, 
-                                    OverlayStatus = FileOverlayStatus.Unversioned, 
+                                    FilePath = filePath.ToLower(),
+                                    OverlayStatus = FileOverlayStatus.Unversioned,
                                     FileStatus = FileStatus.Created,
-                                    Id=Guid.NewGuid()
+                                    Id = Guid.NewGuid()
                                 };
 
 
-            return fileState.UpdateHashesAsync(blockSize,algorithm);            
+            return fileState.UpdateHashesAsync(blockSize, algorithm);
         }
 
-        public Task<FileState> UpdateHashesAsync(int blockSize,string algorithm)
+        public async Task<FileState> UpdateHashesAsync(int blockSize, string algorithm)
         {
-            if (blockSize<=0)
+            if (blockSize <= 0)
                 throw new ArgumentOutOfRangeException("blockSize");
             if (String.IsNullOrWhiteSpace(algorithm))
                 throw new ArgumentNullException("algorithm");
             Contract.EndContractBlock();
-            
+
             //Skip updating the hash for folders
             if (Directory.Exists(FilePath))
-                return Task.Factory.FromResult(this);
+                return this;
 
-            var results = Task.Factory.StartNew(() =>
-            {
-                var info = new FileInfo(FilePath);
-                return info.CalculateHash(blockSize, algorithm);
-            });
+            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)
+        {
+            int retries = 3;
+            while (retries > 0)
+                try
+                {
+                    using (new SessionScope())
+                    {
+                        Execute(call, state);
+                    }
+                }
+                catch (ActiveRecordException exc)
+                {
+                    retries--;
+                    if (retries <= 0)
+                        throw;
+                }
+                catch (Exception exc)
+                {
+                    throw;
+                }
 
-            var state=results.Then(hash =>
-            {
-                Checksum = hash;
-                return Task.Factory.FromResult(this);
-            });
-            
-            return state;
         }
     }
 
@@ -159,23 +412,5 @@ namespace Pithos.Core
         public FileState FileState { get; set; }
 
     }
-    
-  /*  [ActiveRecord("hashes")]
-    public class FileHash : ActiveRecordLinqBase<FileHash>
-    {
-        [PrimaryKey]
-        public int Id { get; set; }
-
-        [Property]
-        public int Order { get; set; }
-
-        [Property]
-        public byte[] Value { get; set; }        
-
-        [BelongsTo("FileStateID")]
-        public FileState FileState { get; set; }
-
-    }*/
-
-
+   
 }