Selective filtering modifications to allow uploading of new root folders
[pithos-ms-client] / trunk / Pithos.Core / Agents / FileAgent.cs
index 903cd02..978ace2 100644 (file)
@@ -57,9 +57,12 @@ namespace Pithos.Core.Agents
     {
         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
 
-        Agent<WorkflowState> _agent;
+        /*
+                Agent<WorkflowState> _agent;
+        */
         private FileSystemWatcher _watcher;
         private FileSystemWatcherAdapter _adapter;
+        private FileEventIdleBatch _eventIdleBatch;
 
         //[Import]
         public IStatusKeeper StatusKeeper { get; set; }
@@ -68,27 +71,70 @@ namespace Pithos.Core.Agents
         //[Import]
         public IPithosWorkflow Workflow { get; set; }
         //[Import]
-        public WorkflowAgent WorkflowAgent { get; set; }
+        //public WorkflowAgent WorkflowAgent { get; set; }
 
         private AccountInfo AccountInfo { get; set; }
 
         internal string RootPath { get;  set; }
         
-        private FileEventIdleBatch _eventIdleBatch;
-
         public TimeSpan IdleTimeout { get; set; }
 
+        public PollAgent PollAgent { get; set; }
 
         private void ProcessBatchedEvents(Dictionary<string, FileSystemEventArgs[]> fileEvents)
         {
+            var paths = fileEvents.Keys;
+
+            PollAgent.SynchNow(paths);
+        }
+
+/*
+        private void ProcessBatchedEvents(Dictionary<string, FileSystemEventArgs[]> fileEvents)
+        {
             StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Uploading {0} files",fileEvents.Count));
-            foreach (var fileEvent in fileEvents)
+            //Start with events that do not originate in one of the ignored folders
+            var initialEvents = from evt in fileEvents
+                              where !IgnorePaths(evt.Key)
+                              select evt;
+
+            IEnumerable<KeyValuePair<string, FileSystemEventArgs[]>> cleanEvents;
+
+            
+            var selectiveEnabled = Selectives.IsSelectiveEnabled(AccountInfo.AccountKey);
+            //When selective sync is enabled,
+            if (selectiveEnabled)
             {
-                var filePath = fileEvent.Key;
+                //Include all selected items
+                var selectedEvents = from evt in initialEvents
+                                     where Selectives.IsSelected(AccountInfo, evt.Key)
+                                     select evt;                
+                //And all folder creations in the unselected folders
+                var folderCreations = from evt in initialEvents
+                                      let folderPath=evt.Key
+                                      //The original folder may not exist due to renames. Just make sure that the path is not a file
+                                      where !File.Exists(folderPath)
+                                            //We only want unselected items
+                                            && !Selectives.IsSelected(AccountInfo, folderPath)
+                                            //Is there any creation event related to the folder?
+                                            && evt.Value.Any(arg => arg.ChangeType == WatcherChangeTypes.Created)
+                                      select evt;
+                cleanEvents = selectedEvents.Union(folderCreations).ToList();
+            }
+            //If selective is disabled, only exclude the shared folders 
+            else
+            {
+                cleanEvents = (from evt in initialEvents
+                              where !evt.Key.IsSharedTo(AccountInfo)
+                              select evt).ToList();
+            }
+
+
+            foreach (var fileEvent in cleanEvents)
+            {
+                //var filePath = fileEvent.Key;
                 var changes = fileEvent.Value;
-                
-                if (Ignore(filePath)) continue;
-                                
+
+                var isNotFile = !File.Exists(fileEvent.Key);
                 foreach (var change in changes)
                 {
                     if (change.ChangeType == WatcherChangeTypes.Renamed)
@@ -105,17 +151,22 @@ namespace Pithos.Core.Agents
                                         });
                     }
                     else
+                    {
+                        var isCreation = selectiveEnabled && isNotFile && change.ChangeType == WatcherChangeTypes.Created;
                         _agent.Post(new WorkflowState(change)
-                        {
-                            AccountInfo = AccountInfo,
-                            Path = change.FullPath,
-                            FileName = Path.GetFileName(change.Name),
-                            TriggeringChange = change.ChangeType
-                        });                        
+                                        {
+                                            AccountInfo = AccountInfo,
+                                            Path = change.FullPath,
+                                            FileName = Path.GetFileName(change.Name),
+                                            TriggeringChange = change.ChangeType,
+                                            IsCreation=isCreation
+                                        });
+                    }
                 }
             }
             StatusNotification.SetPithosStatus(PithosStatus.LocalComplete);
         }
+*/
 
         public void Start(AccountInfo accountInfo,string rootPath)
         {
@@ -127,14 +178,14 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("rootPath must be an absolute path","rootPath");
             if (IdleTimeout == null)
                 throw new InvalidOperationException("IdleTimeout must have a valid value");
-            Contract.EndContractBlock();
+                Contract.EndContractBlock();
 
             AccountInfo = accountInfo;
             RootPath = rootPath;
-
+            
             _eventIdleBatch = new FileEventIdleBatch((int)IdleTimeout.TotalMilliseconds, ProcessBatchedEvents);
 
-            _watcher = new FileSystemWatcher(rootPath) {IncludeSubdirectories = true,InternalBufferSize=8*4096};
+            _watcher = new FileSystemWatcher(rootPath) { IncludeSubdirectories = true, InternalBufferSize = 8 * 4096 };
             _adapter = new FileSystemWatcherAdapter(_watcher);
 
             _adapter.Changed += OnFileEvent;
@@ -144,6 +195,9 @@ namespace Pithos.Core.Agents
             _adapter.Moved += OnMoveEvent;
             _watcher.EnableRaisingEvents = true;
 
+/*            
+
+
 
             _agent = Agent<WorkflowState>.Start(inbox =>
             {
@@ -156,9 +210,10 @@ namespace Pithos.Core.Agents
                         Log.ErrorFormat("[ERROR] File Event Processing:\r{0}", ex));
                 };
                 loop();
-            });
+            });*/
         }
 
+/*
         private Task<object> Process(WorkflowState state)
         {
             if (state==null)
@@ -216,6 +271,7 @@ namespace Pithos.Core.Agents
                     _watcher.EnableRaisingEvents = !value;                
             }
         }
+*/
 
         public string CachePath { get; set; }
 
@@ -229,6 +285,7 @@ namespace Pithos.Core.Agents
         public Selectives Selectives { get; set; }
 
 
+/*
         public void Post(WorkflowState workflowState)
         {
             if (workflowState == null)
@@ -250,12 +307,14 @@ namespace Pithos.Core.Agents
                 _agent.Stop();
         }
 
+*/
         // Enumerate all files in the Pithos directory except those in the Fragment folder
         // and files with a .ignore extension
         public IEnumerable<string> EnumerateFiles(string searchPattern="*")
         {
             var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
                                  where !Ignore(filePath)
+                                 orderby filePath ascending 
                                  select filePath;
             return monitoredFiles;
         }
@@ -265,15 +324,35 @@ namespace Pithos.Core.Agents
             var rootDir = new DirectoryInfo(RootPath);
             var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
                                  where !Ignore(file.FullName)
+                                 orderby file.FullName ascending 
                                  select file;
             return monitoredFiles;
         }                
 
+        public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern="*")
+        {
+            var rootDir = new DirectoryInfo(RootPath);
+            //Ensure folders appear first, to allow folder processing as soon as possilbe
+            var folders = (from file in rootDir.EnumerateDirectories(searchPattern, SearchOption.AllDirectories)
+                                     where !Ignore(file.FullName)
+                                     orderby file.FullName ascending
+                                     select file).ToList();
+            var files = (from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
+                                  where !Ignore(file.FullName)
+                                  orderby file.Length ascending
+                                  select file as FileSystemInfo).ToList();
+            var monitoredFiles = folders
+                                 //Process small files first, leaving expensive large files for last
+                                 .Concat(files);
+            return monitoredFiles;
+        }                
+
         public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
         {
             var rootDir = new DirectoryInfo(RootPath);
             var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
                                  where !Ignore(file.FullName)
+                                 orderby file.FullName ascending 
                                  select file.AsRelativeUrlTo(RootPath);
             return monitoredFiles;
         }                
@@ -283,6 +362,7 @@ namespace Pithos.Core.Agents
             var rootDir = new DirectoryInfo(RootPath);
             var monitoredFiles = from file in rootDir.EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories)
                                  where !Ignore(file.FullName)
+                                 orderby file.FullName ascending 
                                  select file.AsRelativeUrlTo(RootPath);
             return monitoredFiles;
         }                
@@ -290,20 +370,28 @@ namespace Pithos.Core.Agents
 
         
 
-        private bool Ignore(string filePath)
+        public bool Ignore(string filePath)
         {
             if (IgnorePaths(filePath)) return true;
 
 
-            //If selective sync is enabled, propagate folder events
-            if (Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) && Directory.Exists(filePath))
-                return false;
+            //If selective sync is enabled, 
+            if (IsUnselectedRootFolder(filePath))
+                    return false;
             //Ignore if selective synchronization is defined, 
             //And the target file is not below any of the selective paths
-            return !Selectives.IsSelected(AccountInfo, filePath);
+            var ignore = !Selectives.IsSelected(AccountInfo, filePath);
+            return ignore;
+        }
+
+        public bool IsUnselectedRootFolder(string filePath)
+        {
+            return Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) //propagate folder events 
+                   && Directory.Exists(filePath) //from the container root folder only. Note, in the first level below the account root path are the containers
+                   && FoundBelowRoot(filePath, RootPath, 2);
         }
 
-        private bool IgnorePaths(string filePath)
+        public bool IgnorePaths(string filePath)
         {
 //Ignore all first-level directories and files (ie at the container folders level)
             if (FoundBelowRoot(filePath, RootPath, 1))
@@ -322,15 +410,9 @@ namespace Pithos.Core.Agents
             //Ignore anything happening in the cache path
             if (filePath.StartsWith(CachePath))
                 return true;
-            if (_ignoreFiles.ContainsKey(filePath.ToLower()))
-                return true;
             
-            //If selective sync is enabled, propagate folder events
-            if (Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) && Directory.Exists(filePath))
-                return false;
-            //Ignore if selective synchronization is defined, 
-            //And the target file is not below any of the selective paths
-            return !Selectives.IsSelected(AccountInfo, filePath);
+            //Finally, ignore events about one of the ignored files
+            return _ignoreFiles.ContainsKey(filePath.ToLower());
         }
 
 /*        private static bool FoundInRoot(string filePath, string rootPath)
@@ -377,18 +459,7 @@ namespace Pithos.Core.Agents
             return false;            
         }
 
-        //Post a Change message for all events except rename
-        void OnFileEvent(object sender, FileSystemEventArgs e)
-        {
-            //Ignore events that affect the cache folder
-            var filePath = e.FullPath;
-            if (Ignore(filePath)) 
-                return;
-            _eventIdleBatch.Post(e);
-        }
-
-
-/*
+        /*
         //Post a Change message for renames containing the old and new names
         void OnRenameEvent(object sender, RenamedEventArgs e)
         {
@@ -407,7 +478,17 @@ namespace Pithos.Core.Agents
                 TriggeringChange = e.ChangeType
             });
         }
-*/
+        */
+
+        //Post a Change message for all events except rename
+        void OnFileEvent(object sender, FileSystemEventArgs e)
+        {
+            //Ignore events that affect the cache folder
+            var filePath = e.FullPath;
+            if (Ignore(filePath))
+                return;
+            _eventIdleBatch.Post(e);
+        }
 
         //Post a Change message for moves containing the old and new names
         void OnMoveEvent(object sender, MovedEventArgs e)
@@ -415,10 +496,12 @@ namespace Pithos.Core.Agents
             var oldFullPath = e.OldFullPath;
             var fullPath = e.FullPath;
             
+
             //If the source path is one of the ignored folders, ignore
             if (IgnorePaths(oldFullPath)) 
                 return;
 
+            //TODO: Must prevent move propagation if the source folder is blocked by selective sync
             //Ignore takes into account Selective Sync
             if (Ignore(fullPath))
                 return;
@@ -474,20 +557,20 @@ namespace Pithos.Core.Agents
             switch (state.Status)
             {
                 case FileStatus.Created:
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash);
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash).Wait();
                     break;
                 case FileStatus.Modified:
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash);
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ShortHash).Wait();
                     break;
                 case FileStatus.Deleted:
                     //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
                     break;
                 case FileStatus.Renamed:
                     this.StatusKeeper.ClearFileStatus(state.OldPath);
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash);
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ShortHash).Wait();
                     break;
                 case FileStatus.Unchanged:
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal,state.ShortHash);
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ShortHash).Wait();
                     break;
             }