Using MD5 to quickly check for local modifications before calculating the expensive...
[pithos-ms-client] / trunk / Pithos.Core / Agents / StatusAgent.cs
index 27b92b7..b95645d 100644 (file)
@@ -55,9 +55,14 @@ using System.Threading.Tasks;
 using Castle.ActiveRecord;
 using Castle.ActiveRecord.Framework;
 using Castle.ActiveRecord.Framework.Config;
+using NHibernate.ByteCode.Castle;
+using NHibernate.Cfg;
+using NHibernate.Cfg.Loquacious;
+using NHibernate.Dialect;
 using Pithos.Interfaces;
 using Pithos.Network;
 using log4net;
+using Environment = System.Environment;
 
 namespace Pithos.Core.Agents
 {
@@ -76,10 +81,8 @@ namespace Pithos.Core.Agents
         public StatusAgent()
         {            
             var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
-            
-
 
-            _pithosDataPath = Path.Combine(appDataPath , "GRNET");
+            _pithosDataPath = Path.Combine(appDataPath , "GRNET\\PITHOS");
             if (!Directory.Exists(_pithosDataPath))
                 Directory.CreateDirectory(_pithosDataPath);
 
@@ -87,8 +90,10 @@ namespace Pithos.Core.Agents
 
             MigrateOldDb(dbPath, appDataPath);
 
+
             var source = GetConfiguration(_pithosDataPath);
             ActiveRecordStarter.Initialize(source,typeof(FileState),typeof(FileTag));
+            
             ActiveRecordStarter.UpdateSchema();
 
 
@@ -102,15 +107,23 @@ namespace Pithos.Core.Agents
 
         private static void MigrateOldDb(string dbPath, string appDataPath)
         {
-            Contract.Requires(!String.IsNullOrWhiteSpace(dbPath));
-            Contract.Requires(!String.IsNullOrWhiteSpace(appDataPath));
+            if(String.IsNullOrWhiteSpace(dbPath))
+                throw new ArgumentNullException("dbPath");
+            if(String.IsNullOrWhiteSpace(appDataPath))
+                throw new ArgumentNullException("appDataPath");
+            Contract.EndContractBlock();
 
             var oldDbPath = Path.Combine(appDataPath, "Pithos", "pithos.db");
             var oldDbInfo = new FileInfo(oldDbPath);
             if (oldDbInfo.Exists && !File.Exists(dbPath))
             {
+                Log.InfoFormat("Moving database from {0} to {1}",oldDbInfo.FullName,dbPath);
                 var oldDirectory = oldDbInfo.Directory;
-                oldDbInfo.MoveTo(dbPath);                
+                oldDbInfo.MoveTo(dbPath);
+                
+                if (Log.IsDebugEnabled)
+                    Log.DebugFormat("Deleting {0}",oldDirectory.FullName);
+                
                 oldDirectory.Delete(true);
             }
         }
@@ -333,6 +346,12 @@ namespace Pithos.Core.Agents
                         command.Parameters.AddWithValue("path", path);
                         
                         var affected = command.ExecuteNonQuery();
+                        if (affected == 0)
+                        {
+                            var createdState = FileState.CreateFor(FileInfoExtensions.FromPath(path));
+                            createdState.FileStatus = status;
+                            createdState.Create();
+                        }
                         return affected;
                     }
                 }
@@ -344,7 +363,7 @@ namespace Pithos.Core.Agents
             }
         }
 
-        private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
+        private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason)
         {
             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
             {
@@ -357,15 +376,24 @@ namespace Pithos.Core.Agents
                     using (
                         var command =
                             new SQLiteCommand(
-                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE ",
+                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus,ConflictReason= :conflictReason where FilePath = :path COLLATE NOCASE ",
                                 connection))
                     {
 
                         command.Parameters.AddWithValue("path", absolutePath);
                         command.Parameters.AddWithValue("fileStatus", fileStatus);
                         command.Parameters.AddWithValue("overlayStatus", overlayStatus);
+                        command.Parameters.AddWithValue("conflictReason", conflictReason);
                         
                         var affected = command.ExecuteNonQuery();
+                        if (affected == 0)
+                        {
+                            var createdState=FileState.CreateFor(FileInfoExtensions.FromPath(absolutePath));
+                            createdState.FileStatus = fileStatus;
+                            createdState.OverlayStatus = overlayStatus;
+                            createdState.ConflictReason = conflictReason;
+                            createdState.Create();  
+                        }
                         return affected;
                     }
                 }
@@ -556,7 +584,7 @@ namespace Pithos.Core.Agents
             _persistenceAgent.Post(() =>FileState.RenameState(oldPath, newPath));
         }*/
 
-        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus)
+        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason)
         {
             if (String.IsNullOrWhiteSpace(path))
                 throw new ArgumentNullException("path");
@@ -567,7 +595,7 @@ namespace Pithos.Core.Agents
             Debug.Assert(!path.Contains(FolderConstants.CacheFolder));
             Debug.Assert(!path.EndsWith(".ignore"));
 
-            _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus));
+            _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus, conflictReason));
         }
 
 /*
@@ -639,19 +667,18 @@ namespace Pithos.Core.Agents
                     else
                     {
                         command.CommandText =
-                            "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,ShortHash,FileStatus,OverlayStatus) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:shortHash,:fileStatus,:overlayStatus)";
+                            "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,ShortHash,FileStatus,OverlayStatus,ObjectID) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:shortHash,:fileStatus,:overlayStatus,:objectID)";
                         command.Parameters.AddWithValue("id", Guid.NewGuid());
                     }
 
                     command.Parameters.AddWithValue("path", path);
-                    command.Parameters.AddWithValue("checksum", objectInfo.Hash);
-                    command.Parameters.AddWithValue("shortHash", "");
+                    command.Parameters.AddWithValue("checksum", objectInfo.X_Object_Hash);
+                    command.Parameters.AddWithValue("shortHash", objectInfo.ETag);
                     command.Parameters.AddWithValue("version", objectInfo.Version);
-                    command.Parameters.AddWithValue("versionTimeStamp",
-                                                    objectInfo.VersionTimestamp);
+                    command.Parameters.AddWithValue("versionTimeStamp", objectInfo.VersionTimestamp);
                     command.Parameters.AddWithValue("fileStatus", FileStatus.Unchanged);
-                    command.Parameters.AddWithValue("overlayStatus",
-                                                    FileOverlayStatus.Normal);
+                    command.Parameters.AddWithValue("overlayStatus", FileOverlayStatus.Normal);
+                    command.Parameters.AddWithValue("objectID",objectInfo.UUID);
 
                     var affected = command.ExecuteNonQuery();
                     return;
@@ -733,7 +760,7 @@ namespace Pithos.Core.Agents
             if (!Path.IsPathRooted(path))
                 throw new ArgumentException("The path must be rooted", "path");
             Contract.EndContractBlock();
-
+            //TODO: May throw if the agent is cleared for some reason. Should never happen
             _persistenceAgent.Post(() => DeleteFolderDirect(path));   
         }
 
@@ -831,6 +858,65 @@ namespace Pithos.Core.Agents
             _persistenceAgent.Post(() => FileState.UpdateChecksum(path, shortHash,checksum));
         }
 
+
+        public void CleanupOrphanStates()
+        {
+            //Orphan states are those that do not correspond to an account, ie. their paths
+            //do not start with the root path of any registered account
+
+            var roots=(from account in Settings.Accounts
+                      select account.RootPath).ToList();
+            
+            var allStates = from state in FileState.Queryable
+                select state.FilePath;
+
+            foreach (var statePath in allStates)
+            {
+                if (!roots.Any(root=>statePath.StartsWith(root,StringComparison.InvariantCultureIgnoreCase)))
+                    this.DeleteDirect(statePath);
+            }
+        }
+
+        public void CleanupStaleStates(AccountInfo accountInfo, List<ObjectInfo> objectInfos)
+        {
+            if (accountInfo == null)
+                throw new ArgumentNullException("accountInfo");
+            if (objectInfos == null)
+                throw new ArgumentNullException("objectInfos");
+            Contract.EndContractBlock();
+            
+
+
+            //Stale states are those that have no corresponding local or server file
+            
+
+            var agent=FileAgent.GetFileAgent(accountInfo);
+
+            var localFiles=agent.EnumerateFiles();
+            var localSet = new HashSet<string>(localFiles);
+
+            //RelativeUrlToFilePath will fail for
+            //infos of accounts, containers which have no Name
+
+            var serverFiles = from info in objectInfos
+                              where info.Name != null
+                              select Path.Combine(accountInfo.AccountPath,info.RelativeUrlToFilePath(accountInfo.UserName));
+            var serverSet = new HashSet<string>(serverFiles);
+
+            var allStates = from state in FileState.Queryable
+                            where state.FilePath.StartsWith(agent.RootPath)
+                            select state.FilePath;
+            var stateSet = new HashSet<string>(allStates);
+            stateSet.ExceptWith(serverSet);
+            stateSet.ExceptWith(localSet);
+
+            foreach (var remainder in stateSet)
+            {
+                DeleteDirect(remainder);
+            }
+
+            
+        }
     }