Added FileSystemWatcherAdapter.cs to raise Moved events from the sequence of Deleted...
authorPanagiotis Kanavos <pkanavos@gmail.com>
Thu, 19 Jan 2012 21:10:35 +0000 (23:10 +0200)
committerPanagiotis Kanavos <pkanavos@gmail.com>
Thu, 19 Jan 2012 21:10:35 +0000 (23:10 +0200)
Modified PreferencesViewModel.cs to URLEncode the token when opening the Pithos site

trunk/Pithos.Client.WPF/Preferences/PreferencesViewModel.cs
trunk/Pithos.Core/Agents/FileAgent.cs
trunk/Pithos.Core/Agents/FileSystemWatcherAdapter.cs [new file with mode: 0644]
trunk/Pithos.Core/Agents/MovedEventArgs.cs [new file with mode: 0644]
trunk/Pithos.Core/Pithos.Core.csproj

index bcd7633..772c9ab 100644 (file)
@@ -93,6 +93,7 @@ namespace Pithos.Client.WPF
 
         private static void CreateShortcut(string shortcutPath)
         {
+               //TODO: ###THROWS PERMISSIONS ERROR###
             var wshShell = new WshShellClass();
             var shortcut = (IWshRuntimeLibrary.IWshShortcut) wshShell.CreateShortcut(
                 shortcutPath);
index 8218cc6..8f2d585 100644 (file)
@@ -19,6 +19,7 @@ namespace Pithos.Core.Agents
     {
         Agent<WorkflowState> _agent;
         private FileSystemWatcher _watcher;
+        private FileSystemWatcherAdapter _adapter;
 
         //[Import]
         public IStatusKeeper StatusKeeper { get; set; }
@@ -45,12 +46,14 @@ namespace Pithos.Core.Agents
 
             AccountInfo = accountInfo;
             RootPath = rootPath;
-            _watcher = new FileSystemWatcher(rootPath);
-            _watcher.IncludeSubdirectories = true;            
-            _watcher.Changed += OnFileEvent;
-            _watcher.Created += OnFileEvent;
-            _watcher.Deleted += OnFileEvent;
-            _watcher.Renamed += OnRenameEvent;
+            _watcher = new FileSystemWatcher(rootPath) {IncludeSubdirectories = true};
+            _adapter = new FileSystemWatcherAdapter(_watcher);
+
+            _adapter.Changed += OnFileEvent;
+            _adapter.Created += OnFileEvent;
+            _adapter.Deleted += OnFileEvent;
+            _adapter.Renamed += OnRenameEvent;
+            _adapter.Moved += OnMoveEvent;
             _watcher.EnableRaisingEvents = true;
 
 
@@ -234,6 +237,25 @@ namespace Pithos.Core.Agents
             });
         }
 
+        //Post a Change message for renames containing the old and new names
+        void OnMoveEvent(object sender, MovedEventArgs e)
+        {
+            var oldFullPath = e.OldFullPath;
+            var fullPath = e.FullPath;
+            if (Ignore(oldFullPath) || Ignore(fullPath))
+                return;
+
+            _agent.Post(new WorkflowState
+            {
+                AccountInfo=AccountInfo,
+                OldPath = oldFullPath,
+                OldFileName = e.OldName,
+                Path = fullPath,
+                FileName = e.Name,
+                TriggeringChange = e.ChangeType
+            });
+        }
+
 
 
         private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
diff --git a/trunk/Pithos.Core/Agents/FileSystemWatcherAdapter.cs b/trunk/Pithos.Core/Agents/FileSystemWatcherAdapter.cs
new file mode 100644 (file)
index 0000000..e652f48
--- /dev/null
@@ -0,0 +1,229 @@
+// -----------------------------------------------------------------------\r
+// <copyright file="FileInfoExtensions.cs" company="GRNET">\r
+// Copyright 2011 GRNET S.A. All rights reserved.\r
+// \r
+// Redistribution and use in source and binary forms, with or\r
+// without modification, are permitted provided that the following\r
+// conditions are met:\r
+// \r
+//   1. Redistributions of source code must retain the above\r
+//      copyright notice, this list of conditions and the following\r
+//      disclaimer.\r
+// \r
+//   2. Redistributions in binary form must reproduce the above\r
+//      copyright notice, this list of conditions and the following\r
+//      disclaimer in the documentation and/or other materials\r
+//      provided with the distribution.\r
+// \r
+// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+// POSSIBILITY OF SUCH DAMAGE.\r
+// \r
+// The views and conclusions contained in the software and\r
+// documentation are those of the authors and should not be\r
+// interpreted as representing official policies, either expressed\r
+// or implied, of GRNET S.A.\r
+// </copyright>\r
+// -----------------------------------------------------------------------\r
+\r
+\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Threading.Tasks;\r
+\r
+namespace Pithos.Core.Agents\r
+{\r
+    using System;\r
+    using System.Collections.Generic;\r
+    using System.Linq;\r
+    using System.Text;\r
+\r
+    /// <summary>\r
+    /// TODO: Update summary.\r
+    /// </summary>\r
+    public class FileSystemWatcherAdapter\r
+    {\r
+         public event FileSystemEventHandler Changed;\r
+        public event FileSystemEventHandler Created;\r
+        public event FileSystemEventHandler Deleted;\r
+        public event RenamedEventHandler Renamed;\r
+        public event MovedEventHandler Moved;\r
+\r
+        public FileSystemWatcherAdapter(FileSystemWatcher watcher)\r
+        {\r
+            if (watcher==null)\r
+                throw new ArgumentNullException("watcher");\r
+            Contract.EndContractBlock();\r
+\r
+            watcher.Changed += OnChangeOrCreate;\r
+            watcher.Created += OnChangeOrCreate;\r
+            watcher.Deleted += OnDeleted;\r
+            watcher.Renamed += OnRename;            \r
+            \r
+        }\r
+\r
+        private string _cachedDeletedFullPath;\r
+        private const int PropagateDelay = 10;        \r
+\r
+        private static void OnTimeout(object state)\r
+        {\r
+            throw new NotImplementedException();\r
+        }\r
+\r
+        private void OnDeleted(object sender, FileSystemEventArgs e)\r
+        {\r
+            if (sender == null)\r
+                throw new ArgumentNullException("sender");\r
+            if (e.ChangeType != WatcherChangeTypes.Deleted)\r
+                throw new ArgumentException("e");\r
+            if (string.IsNullOrWhiteSpace(e.FullPath))\r
+                throw new ArgumentException("e");\r
+            Contract.Ensures(!String.IsNullOrWhiteSpace(_cachedDeletedFullPath));\r
+            Contract.EndContractBlock();\r
+\r
+            //Handle any previously deleted event\r
+            PropagateCachedDeleted(sender);\r
+\r
+            //A delete event may be an actual delete event or the first event in a move action.\r
+            //To decide which action occured, we need to wait for the next action, so\r
+            //we  store the file path and return .\r
+            //A delete action will not be followed by any other event, so we need to add a watchdog \r
+            //that will declare a Delete action after a short amount of time\r
+\r
+            //TODO: Moving a folder to the recycle bin results in a single delete event for the entire folder and its contents\r
+            //      as this is actually a MOVE operation\r
+            //Deleting by Shift+Delete results in a delete event for each file followed by the delete of the folder itself\r
+            _cachedDeletedFullPath = e.FullPath;\r
+\r
+            //TODO: This requires synchronization of the _cachedDeletedFullPath field\r
+            //TODO: This creates a new task for each file even though we can cancel any existing tasks if a new event arrives\r
+            //Maybe, use a timer instead of a task\r
+            \r
+            TaskEx.Delay(PropagateDelay).ContinueWith(t =>\r
+                                                           {\r
+                                                               var myPath = e.FullPath;\r
+                                                               if (this._cachedDeletedFullPath==myPath)\r
+                                                                    PropagateCachedDeleted(sender);\r
+                                                           });\r
+        }\r
+\r
+        private void OnRename(object sender, RenamedEventArgs e)\r
+        {\r
+            if (sender == null)\r
+                throw new ArgumentNullException("sender");\r
+            Contract.Ensures(_cachedDeletedFullPath == null);\r
+            Contract.EndContractBlock();\r
+\r
+            try\r
+            {\r
+                //Propagate any previous cached delete event\r
+                PropagateCachedDeleted(sender);\r
+                \r
+                if (Renamed!=null)\r
+                    Renamed(sender, e);\r
+                \r
+            }\r
+            finally\r
+            {\r
+                _cachedDeletedFullPath = null;    \r
+            }\r
+        }\r
+\r
+        private void OnChangeOrCreate(object sender, FileSystemEventArgs e)\r
+        {\r
+            if (sender == null)\r
+                throw new ArgumentNullException("sender");\r
+            if (!(e.ChangeType == WatcherChangeTypes.Created || e.ChangeType == WatcherChangeTypes.Changed))\r
+                throw new ArgumentException("e");\r
+            Contract.Ensures(_cachedDeletedFullPath == null);\r
+            Contract.EndContractBlock();\r
+\r
+            try\r
+            {\r
+                //A Move action results in a sequence of a Delete and a Create or Change event\r
+                //If the actual action is a Move, raise a Move event instead of the actual event\r
+                if (HandleMoved(sender, e))\r
+                    return;\r
+\r
+                //Otherwise, propagate the Delete event if it exists \r
+                PropagateCachedDeleted(sender);\r
+                //and propagate the actual event\r
+                var actualEvent = e.ChangeType == WatcherChangeTypes.Created ? Created : Changed;\r
+                if (actualEvent != null)\r
+                    actualEvent(sender, e);\r
+            }\r
+            finally\r
+            {\r
+                //Finally, make sure the cached path is cleared\r
+                _cachedDeletedFullPath = null;\r
+            }\r
+\r
+        }\r
+\r
+        private bool HandleMoved(object sender, FileSystemEventArgs e)\r
+        {\r
+            if (sender == null)\r
+                throw new ArgumentNullException("sender");\r
+            if (!(e.ChangeType == WatcherChangeTypes.Created ||\r
+                                                  e.ChangeType == WatcherChangeTypes.Changed))\r
+                throw new ArgumentException("e");\r
+            Contract.EndContractBlock();\r
+\r
+            //TODO: If a file is deleted and another file with the same name is created, it will be detected as a MOVE\r
+            //instead of a sequence of independent actions\r
+            //One way to detect this would be to request that the full paths are NOT the same\r
+\r
+            var oldName = Path.GetFileName(_cachedDeletedFullPath);\r
+            //NOTE: e.Name is a path relative to the watched path. We MUST call Path.GetFileName to get the actual path\r
+            var newName = Path.GetFileName(e.Name);\r
+            //If the last deleted filename is equal to the current and the action is create, we have a MOVE operation\r
+            var hasMoved = (_cachedDeletedFullPath != e.FullPath && oldName == newName);\r
+\r
+            if (!hasMoved)\r
+                return false;\r
+\r
+            try\r
+            {\r
+                //If the actual action is a Move, raise a Move event instead of the actual event\r
+                var newDirectory = Path.GetDirectoryName(e.FullPath);\r
+                var oldDirectory = Path.GetDirectoryName(_cachedDeletedFullPath);\r
+                if (Moved != null)\r
+                    Moved(sender, new MovedEventArgs(newDirectory, newName, oldDirectory, oldName));\r
+            }\r
+            finally\r
+            {\r
+                _cachedDeletedFullPath = null;\r
+            }\r
+            return true;\r
+        }\r
+\r
+        private void PropagateCachedDeleted(object sender)\r
+        {\r
+            if (sender == null)\r
+                throw new ArgumentNullException("sender");\r
+            Contract.Ensures(_cachedDeletedFullPath == null);\r
+            Contract.EndContractBlock();\r
+\r
+            //Nothing to handle if there is no cached deleted file\r
+            if (String.IsNullOrWhiteSpace(_cachedDeletedFullPath))\r
+                return;\r
+            \r
+            var deletedFileName = Path.GetFileName(_cachedDeletedFullPath);\r
+            var deletedFileDirectory = Path.GetDirectoryName(_cachedDeletedFullPath);\r
+\r
+            if (Deleted!=null)\r
+                Deleted(sender, new FileSystemEventArgs(WatcherChangeTypes.Deleted, deletedFileDirectory, deletedFileName));\r
+\r
+            _cachedDeletedFullPath = null;\r
+        }\r
+    }\r
+}\r
diff --git a/trunk/Pithos.Core/Agents/MovedEventArgs.cs b/trunk/Pithos.Core/Agents/MovedEventArgs.cs
new file mode 100644 (file)
index 0000000..9005450
--- /dev/null
@@ -0,0 +1,82 @@
+// -----------------------------------------------------------------------\r
+// <copyright file="FileInfoExtensions.cs" company="GRNET">\r
+// Copyright 2011 GRNET S.A. All rights reserved.\r
+// \r
+// Redistribution and use in source and binary forms, with or\r
+// without modification, are permitted provided that the following\r
+// conditions are met:\r
+// \r
+//   1. Redistributions of source code must retain the above\r
+//      copyright notice, this list of conditions and the following\r
+//      disclaimer.\r
+// \r
+//   2. Redistributions in binary form must reproduce the above\r
+//      copyright notice, this list of conditions and the following\r
+//      disclaimer in the documentation and/or other materials\r
+//      provided with the distribution.\r
+// \r
+// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+// POSSIBILITY OF SUCH DAMAGE.\r
+// \r
+// The views and conclusions contained in the software and\r
+// documentation are those of the authors and should not be\r
+// interpreted as representing official policies, either expressed\r
+// or implied, of GRNET S.A.\r
+// </copyright>\r
+// -----------------------------------------------------------------------\r
+\r
+\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Security.Permissions;\r
+using System;\r
+\r
+namespace Pithos.Core.Agents\r
+{\r
+    \r
+\r
+    public delegate void MovedEventHandler(object sender, MovedEventArgs e);\r
+\r
+    public class MovedEventArgs : FileSystemEventArgs\r
+    {\r
+        private readonly string _oldName;\r
+        private readonly string _oldFullPath;\r
+\r
+        public string OldFullPath\r
+        {\r
+            get\r
+            {\r
+                new FileIOPermission(FileIOPermissionAccess.Read, Path.GetPathRoot(_oldFullPath)).Demand();\r
+                return _oldFullPath;\r
+            }\r
+        }\r
+\r
+        public string OldName\r
+        {\r
+            get { return _oldName; }\r
+        }\r
+\r
+        public MovedEventArgs(string newDirectory, string newName, string oldDirectory, string oldName)\r
+            : base(WatcherChangeTypes.Renamed, newDirectory, newName)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(oldDirectory))\r
+                throw new ArgumentNullException("oldDirectory");\r
+            if (String.IsNullOrWhiteSpace(oldName))\r
+                throw new ArgumentNullException("oldName");\r
+            Contract.EndContractBlock();\r
+\r
+            _oldName = oldName;\r
+            _oldFullPath = Path.Combine(oldDirectory, oldName);\r
+        }\r
+    }\r
+}\r
index 151f573..6558320 100644 (file)
     <Compile Include="Agents\CollectionExtensions.cs" />
     <Compile Include="Agents\FileAgent.cs" />
     <Compile Include="Agents\FileInfoExtensions.cs" />
+    <Compile Include="Agents\FileSystemWatcherAdapter.cs" />
+    <Compile Include="Agents\MovedEventArgs.cs" />
     <Compile Include="Agents\NetworkAgent.cs" />
     <Compile Include="Agents\WorkflowAgent.cs" />
     <Compile Include="DynamicDictionary.cs" />