Preliminary fix for #1999, incorrect deletions
authorPanagiotis Kanavos <pkanavos@gmail.com>
Thu, 9 Feb 2012 21:36:14 +0000 (23:36 +0200)
committerPanagiotis Kanavos <pkanavos@gmail.com>
Thu, 9 Feb 2012 21:36:14 +0000 (23:36 +0200)
The first attempt is to detect deletions AFTER the first poll by comparing a cached listing to the new listing. Missing files are deletes. Can be expanded to simplify the rest of the processing code as it can also yield new files and modified files (same path, higher versions or different lengths or hashes)

trunk/Pithos.Client.WPF/Properties/AssemblyInfo.cs
trunk/Pithos.Core/Agents/CloudTransferAction.cs
trunk/Pithos.Core/Agents/NetworkAgent.cs

index 9de2288..ab55d1b 100644 (file)
@@ -53,4 +53,4 @@ using System.Windows;
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 [assembly: AssemblyVersion("0.1.0.0")]
-[assembly: AssemblyFileVersionAttribute("0.1.20206.1972")]
+[assembly: AssemblyFileVersionAttribute("0.1.20209.1999")]
index c14d3d8..d2fa100 100644 (file)
@@ -44,6 +44,11 @@ namespace Pithos.Core.Agents
             Contract.Invariant(AccountInfo!=null);
         }
 
+        public bool IsShared
+        {
+            get { return  CloudFile!=null && AccountInfo.UserName != CloudFile.Account; }
+        }
+
         protected CloudAction(AccountInfo accountInfo,CloudActionType action)
         {
             if (accountInfo==null)
index 8665616..123923c 100644 (file)
@@ -642,6 +642,24 @@ namespace Pithos.Core.Agents
             }
         }
 
+        internal class ObjectInfoComparer:EqualityComparer<ObjectInfo>
+        {
+            public override bool Equals(ObjectInfo x, ObjectInfo y)
+            {
+                return (x.Account == y.Account &&
+                        x.Container == y.Container &&
+                        x.Name == y.Name);
+            }
+
+            public override int GetHashCode(ObjectInfo obj)
+            {
+                return String.Join("/",obj.Account,obj.Container,obj.Name).GetHashCode();
+            }
+        }
+
+        Dictionary<string, List<ObjectInfo>> _currentSnapshot = new Dictionary<string, List<ObjectInfo>>();
+        Dictionary<string, List<ObjectInfo>> _previousSnapshot = new Dictionary<string, List<ObjectInfo>>();
+
         /// <summary>
         /// Deletes local files that are not found in the list of cloud files
         /// </summary>
@@ -658,54 +676,37 @@ namespace Pithos.Core.Agents
                 throw new ArgumentNullException("cloudFiles");
             Contract.EndContractBlock();
 
-            //Check the Modified date to ensure that were just created and haven't been uploaded yet
-            //NOTE: The NHibernate LINQ provider doesn't support custom functions so we need to break the query 
-            //in two steps
-            //NOTE: DON'T return files that are already in conflict. The first poll would mark them as 
-            //"In Conflict" but subsequent polls would delete them
-/*            var t=FileState.Find(new Guid("{cd664c9a-5f17-47c9-b27f-3bcbcb0595ff}"));
+            if (_previousSnapshot.ContainsKey(accountInfo.UserName) && _currentSnapshot.ContainsKey(accountInfo.UserName))
+                _previousSnapshot[accountInfo.UserName] = _currentSnapshot[accountInfo.UserName] ?? new List<ObjectInfo>();
+            else
+            {
+                _previousSnapshot[accountInfo.UserName]=new List<ObjectInfo>();
+            }
 
-            var d0 = FileState.Queryable
-                .Where(state => 
-                            state.FilePath.StartsWith(accountInfo.AccountPath)).ToList();
-            
-            var d1 = FileState.Queryable
-                .Where(state => state.Modified <= pollTime).ToList();
-            var d2= FileState.Queryable
-                .Where(state => state.Modified <= pollTime
-                            &&
-                            state.FilePath.StartsWith(accountInfo.AccountPath)).ToList();*/
-
-            //Consider for deleteion only files modified before the PREVIOUS poll
-            //A user may perform a file creation or rename at roughly the same time as a poll. In such a case
-            //the new file will appear as deleted
-            var previousPollTime = pollTime.Subtract(TimeSpan.FromMilliseconds(Settings.PollingInterval));                       
-
-            //Only consider files that are not being modified, ie they are in the Unchanged state            
-            var deleteCandidates = FileState.Queryable.Where(state => 
-                state.Modified <= previousPollTime
-                && state.FilePath.StartsWith(accountInfo.AccountPath)                
-                && state.FileStatus == FileStatus.Unchanged).ToList();
-
-            //TODO: filesToDelete must take into account the Others container            
-            var filesToDelete = (from deleteCandidate in deleteCandidates 
-                         let localFile = FileInfoExtensions.FromPath(deleteCandidate.FilePath) 
-                         let relativeFilePath = localFile.AsRelativeTo(accountInfo.AccountPath) 
-                         let agentActions = _agent.GetEnumerable()
-                         where 
-                                 !_lastSeen.ContainsKey(localFile.FullName)
-                         && !cloudFiles.Any(r => r.RelativeUrlToFilePath(accountInfo.UserName) == relativeFilePath ) 
-                         //Exclude files enqueued for uploading
-                         //Large files will not appear on the server for multiple polls. They must not be marked as deleted
-                         && !agentActions.Any(action => action.LocalFile.WithProperCapitalization().FullName == localFile.FullName)
-                         //Do NOT delete files modified since the previous poll
-                                && localFile.LastAccessTime < previousPollTime
-                         select localFile).ToList();
-            
+            _currentSnapshot[accountInfo.UserName] = cloudFiles.ToList();
+
+            var deletedObjects = _previousSnapshot[accountInfo.UserName].Except(_currentSnapshot[accountInfo.UserName], new ObjectInfoComparer()).ToList();
 
+            
             //On the first run
             if (_firstPoll)
             {
+                //Only consider files that are not being modified, ie they are in the Unchanged state            
+                var deleteCandidates = FileState.Queryable.Where(state =>
+                    state.FilePath.StartsWith(accountInfo.AccountPath)
+                    && state.FileStatus == FileStatus.Unchanged).ToList();
+
+
+                //TODO: filesToDelete must take into account the Others container            
+                var filesToDelete = (from deleteCandidate in deleteCandidates
+                                         let localFile = FileInfoExtensions.FromPath(deleteCandidate.FilePath)
+                                         let relativeFilePath = localFile.AsRelativeTo(accountInfo.AccountPath)
+                                     where
+                                         !cloudFiles.Any(r => r.RelativeUrlToFilePath(accountInfo.UserName) == relativeFilePath)
+                                     select localFile).ToList();
+            
+
+
                 //Set the status of missing files to Conflict
                 foreach (var item in filesToDelete)
                 {
@@ -720,32 +721,44 @@ namespace Pithos.Core.Agents
                 }
                 UpdateStatus(PithosStatus.HasConflicts);
                 StatusNotification.NotifyConflicts(filesToDelete, String.Format("{0} local files are missing from Pithos, possibly because they were deleted",filesToDelete.Count));
+                StatusNotification.NotifyForFiles(filesToDelete, String.Format("{0} files were deleted", filesToDelete.Count), TraceLevel.Info);
             }
             else
             {
-                foreach (var item in filesToDelete)
+                var deletedFiles = new List<FileSystemInfo>();
+                foreach (var objectInfo in deletedObjects)
                 {
+                    var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
+                    var item = GetFileAgent(accountInfo).GetFileSystemInfo(relativePath);
                     if (item.Exists)
                     {
                         //Try to acquire a gate on the file, to take into account files that have been dequeued
                         //and are being processed
                         //TODO: The gate is not enough. Perhaps we need to keep a journal of processed files and check against
                         //that as well.
+/*
                         using (var gate = NetworkGate.Acquire(item.FullName, NetworkOperation.Deleting))
                         {
                             if (gate.Failed)
                                 continue;
+*/
                             if ((item.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                             {
                                 item.Attributes = item.Attributes & ~FileAttributes.ReadOnly;
 
                             }
                             item.Delete();
+                            DateTime lastDate;
+                            _lastSeen.TryRemove(item.FullName, out lastDate);
+                            deletedFiles.Add(item);
+/*
                         }
+*/
                     }
                     StatusKeeper.ClearFileStatus(item.FullName);
+                    
                 }
-                StatusNotification.NotifyForFiles(filesToDelete, String.Format("{0} files were deleted",filesToDelete.Count),TraceLevel.Info);
+                StatusNotification.NotifyForFiles(deletedFiles, String.Format("{0} files were deleted", deletedFiles.Count), TraceLevel.Info);
             }
 
         }
@@ -1236,8 +1249,12 @@ namespace Pithos.Core.Agents
 
         }
 
+        //Returns true if an action concerns a file that was deleted
         private bool IsDeletedFile(CloudAction action)
-        {            
+        {
+            //Doesn't work for actions targeting shared files
+            if (action.IsShared)
+                return false;
             var key = GetFileKey(action.CloudFile);
             DateTime entryDate;
             if (_deletedFiles.TryGetValue(key, out entryDate))