Revision 78ebfd2d

b/trunk/Pithos.Client.WPF/Preferences/PreferencesViewModel.cs
93 93

  
94 94
        private static void CreateShortcut(string shortcutPath)
95 95
        {
96
		//TODO: ###THROWS PERMISSIONS ERROR###
96 97
            var wshShell = new WshShellClass();
97 98
            var shortcut = (IWshRuntimeLibrary.IWshShortcut) wshShell.CreateShortcut(
98 99
                shortcutPath);
b/trunk/Pithos.Core/Agents/FileAgent.cs
19 19
    {
20 20
        Agent<WorkflowState> _agent;
21 21
        private FileSystemWatcher _watcher;
22
        private FileSystemWatcherAdapter _adapter;
22 23

  
23 24
        //[Import]
24 25
        public IStatusKeeper StatusKeeper { get; set; }
......
45 46

  
46 47
            AccountInfo = accountInfo;
47 48
            RootPath = rootPath;
48
            _watcher = new FileSystemWatcher(rootPath);
49
            _watcher.IncludeSubdirectories = true;            
50
            _watcher.Changed += OnFileEvent;
51
            _watcher.Created += OnFileEvent;
52
            _watcher.Deleted += OnFileEvent;
53
            _watcher.Renamed += OnRenameEvent;
49
            _watcher = new FileSystemWatcher(rootPath) {IncludeSubdirectories = true};
50
            _adapter = new FileSystemWatcherAdapter(_watcher);
51

  
52
            _adapter.Changed += OnFileEvent;
53
            _adapter.Created += OnFileEvent;
54
            _adapter.Deleted += OnFileEvent;
55
            _adapter.Renamed += OnRenameEvent;
56
            _adapter.Moved += OnMoveEvent;
54 57
            _watcher.EnableRaisingEvents = true;
55 58

  
56 59

  
......
234 237
            });
235 238
        }
236 239

  
240
        //Post a Change message for renames containing the old and new names
241
        void OnMoveEvent(object sender, MovedEventArgs e)
242
        {
243
            var oldFullPath = e.OldFullPath;
244
            var fullPath = e.FullPath;
245
            if (Ignore(oldFullPath) || Ignore(fullPath))
246
                return;
247

  
248
            _agent.Post(new WorkflowState
249
            {
250
                AccountInfo=AccountInfo,
251
                OldPath = oldFullPath,
252
                OldFileName = e.OldName,
253
                Path = fullPath,
254
                FileName = e.Name,
255
                TriggeringChange = e.ChangeType
256
            });
257
        }
258

  
237 259

  
238 260

  
239 261
        private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
b/trunk/Pithos.Core/Agents/FileSystemWatcherAdapter.cs
1
// -----------------------------------------------------------------------
2
// <copyright file="FileInfoExtensions.cs" company="GRNET">
3
// Copyright 2011 GRNET S.A. All rights reserved.
4
// 
5
// Redistribution and use in source and binary forms, with or
6
// without modification, are permitted provided that the following
7
// conditions are met:
8
// 
9
//   1. Redistributions of source code must retain the above
10
//      copyright notice, this list of conditions and the following
11
//      disclaimer.
12
// 
13
//   2. Redistributions in binary form must reproduce the above
14
//      copyright notice, this list of conditions and the following
15
//      disclaimer in the documentation and/or other materials
16
//      provided with the distribution.
17
// 
18
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
// POSSIBILITY OF SUCH DAMAGE.
30
// 
31
// The views and conclusions contained in the software and
32
// documentation are those of the authors and should not be
33
// interpreted as representing official policies, either expressed
34
// or implied, of GRNET S.A.
35
// </copyright>
36
// -----------------------------------------------------------------------
37

  
38

  
39
using System.Diagnostics.Contracts;
40
using System.IO;
41
using System.Threading.Tasks;
42

  
43
namespace Pithos.Core.Agents
44
{
45
    using System;
46
    using System.Collections.Generic;
47
    using System.Linq;
48
    using System.Text;
49

  
50
    /// <summary>
51
    /// TODO: Update summary.
52
    /// </summary>
53
    public class FileSystemWatcherAdapter
54
    {
55
         public event FileSystemEventHandler Changed;
56
        public event FileSystemEventHandler Created;
57
        public event FileSystemEventHandler Deleted;
58
        public event RenamedEventHandler Renamed;
59
        public event MovedEventHandler Moved;
60

  
61
        public FileSystemWatcherAdapter(FileSystemWatcher watcher)
62
        {
63
            if (watcher==null)
64
                throw new ArgumentNullException("watcher");
65
            Contract.EndContractBlock();
66

  
67
            watcher.Changed += OnChangeOrCreate;
68
            watcher.Created += OnChangeOrCreate;
69
            watcher.Deleted += OnDeleted;
70
            watcher.Renamed += OnRename;            
71
            
72
        }
73

  
74
        private string _cachedDeletedFullPath;
75
        private const int PropagateDelay = 10;        
76

  
77
        private static void OnTimeout(object state)
78
        {
79
            throw new NotImplementedException();
80
        }
81

  
82
        private void OnDeleted(object sender, FileSystemEventArgs e)
83
        {
84
            if (sender == null)
85
                throw new ArgumentNullException("sender");
86
            if (e.ChangeType != WatcherChangeTypes.Deleted)
87
                throw new ArgumentException("e");
88
            if (string.IsNullOrWhiteSpace(e.FullPath))
89
                throw new ArgumentException("e");
90
            Contract.Ensures(!String.IsNullOrWhiteSpace(_cachedDeletedFullPath));
91
            Contract.EndContractBlock();
92

  
93
            //Handle any previously deleted event
94
            PropagateCachedDeleted(sender);
95

  
96
            //A delete event may be an actual delete event or the first event in a move action.
97
            //To decide which action occured, we need to wait for the next action, so
98
            //we  store the file path and return .
99
            //A delete action will not be followed by any other event, so we need to add a watchdog 
100
            //that will declare a Delete action after a short amount of time
101

  
102
            //TODO: Moving a folder to the recycle bin results in a single delete event for the entire folder and its contents
103
            //      as this is actually a MOVE operation
104
            //Deleting by Shift+Delete results in a delete event for each file followed by the delete of the folder itself
105
            _cachedDeletedFullPath = e.FullPath;
106

  
107
            //TODO: This requires synchronization of the _cachedDeletedFullPath field
108
            //TODO: This creates a new task for each file even though we can cancel any existing tasks if a new event arrives
109
            //Maybe, use a timer instead of a task
110
            
111
            TaskEx.Delay(PropagateDelay).ContinueWith(t =>
112
                                                           {
113
                                                               var myPath = e.FullPath;
114
                                                               if (this._cachedDeletedFullPath==myPath)
115
                                                                    PropagateCachedDeleted(sender);
116
                                                           });
117
        }
118

  
119
        private void OnRename(object sender, RenamedEventArgs e)
120
        {
121
            if (sender == null)
122
                throw new ArgumentNullException("sender");
123
            Contract.Ensures(_cachedDeletedFullPath == null);
124
            Contract.EndContractBlock();
125

  
126
            try
127
            {
128
                //Propagate any previous cached delete event
129
                PropagateCachedDeleted(sender);
130
                
131
                if (Renamed!=null)
132
                    Renamed(sender, e);
133
                
134
            }
135
            finally
136
            {
137
                _cachedDeletedFullPath = null;    
138
            }
139
        }
140

  
141
        private void OnChangeOrCreate(object sender, FileSystemEventArgs e)
142
        {
143
            if (sender == null)
144
                throw new ArgumentNullException("sender");
145
            if (!(e.ChangeType == WatcherChangeTypes.Created || e.ChangeType == WatcherChangeTypes.Changed))
146
                throw new ArgumentException("e");
147
            Contract.Ensures(_cachedDeletedFullPath == null);
148
            Contract.EndContractBlock();
149

  
150
            try
151
            {
152
                //A Move action results in a sequence of a Delete and a Create or Change event
153
                //If the actual action is a Move, raise a Move event instead of the actual event
154
                if (HandleMoved(sender, e))
155
                    return;
156

  
157
                //Otherwise, propagate the Delete event if it exists 
158
                PropagateCachedDeleted(sender);
159
                //and propagate the actual event
160
                var actualEvent = e.ChangeType == WatcherChangeTypes.Created ? Created : Changed;
161
                if (actualEvent != null)
162
                    actualEvent(sender, e);
163
            }
164
            finally
165
            {
166
                //Finally, make sure the cached path is cleared
167
                _cachedDeletedFullPath = null;
168
            }
169

  
170
        }
171

  
172
        private bool HandleMoved(object sender, FileSystemEventArgs e)
173
        {
174
            if (sender == null)
175
                throw new ArgumentNullException("sender");
176
            if (!(e.ChangeType == WatcherChangeTypes.Created ||
177
                                                  e.ChangeType == WatcherChangeTypes.Changed))
178
                throw new ArgumentException("e");
179
            Contract.EndContractBlock();
180

  
181
            //TODO: If a file is deleted and another file with the same name is created, it will be detected as a MOVE
182
            //instead of a sequence of independent actions
183
            //One way to detect this would be to request that the full paths are NOT the same
184

  
185
            var oldName = Path.GetFileName(_cachedDeletedFullPath);
186
            //NOTE: e.Name is a path relative to the watched path. We MUST call Path.GetFileName to get the actual path
187
            var newName = Path.GetFileName(e.Name);
188
            //If the last deleted filename is equal to the current and the action is create, we have a MOVE operation
189
            var hasMoved = (_cachedDeletedFullPath != e.FullPath && oldName == newName);
190

  
191
            if (!hasMoved)
192
                return false;
193

  
194
            try
195
            {
196
                //If the actual action is a Move, raise a Move event instead of the actual event
197
                var newDirectory = Path.GetDirectoryName(e.FullPath);
198
                var oldDirectory = Path.GetDirectoryName(_cachedDeletedFullPath);
199
                if (Moved != null)
200
                    Moved(sender, new MovedEventArgs(newDirectory, newName, oldDirectory, oldName));
201
            }
202
            finally
203
            {
204
                _cachedDeletedFullPath = null;
205
            }
206
            return true;
207
        }
208

  
209
        private void PropagateCachedDeleted(object sender)
210
        {
211
            if (sender == null)
212
                throw new ArgumentNullException("sender");
213
            Contract.Ensures(_cachedDeletedFullPath == null);
214
            Contract.EndContractBlock();
215

  
216
            //Nothing to handle if there is no cached deleted file
217
            if (String.IsNullOrWhiteSpace(_cachedDeletedFullPath))
218
                return;
219
            
220
            var deletedFileName = Path.GetFileName(_cachedDeletedFullPath);
221
            var deletedFileDirectory = Path.GetDirectoryName(_cachedDeletedFullPath);
222

  
223
            if (Deleted!=null)
224
                Deleted(sender, new FileSystemEventArgs(WatcherChangeTypes.Deleted, deletedFileDirectory, deletedFileName));
225

  
226
            _cachedDeletedFullPath = null;
227
        }
228
    }
229
}
b/trunk/Pithos.Core/Agents/MovedEventArgs.cs
1
// -----------------------------------------------------------------------
2
// <copyright file="FileInfoExtensions.cs" company="GRNET">
3
// Copyright 2011 GRNET S.A. All rights reserved.
4
// 
5
// Redistribution and use in source and binary forms, with or
6
// without modification, are permitted provided that the following
7
// conditions are met:
8
// 
9
//   1. Redistributions of source code must retain the above
10
//      copyright notice, this list of conditions and the following
11
//      disclaimer.
12
// 
13
//   2. Redistributions in binary form must reproduce the above
14
//      copyright notice, this list of conditions and the following
15
//      disclaimer in the documentation and/or other materials
16
//      provided with the distribution.
17
// 
18
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
// POSSIBILITY OF SUCH DAMAGE.
30
// 
31
// The views and conclusions contained in the software and
32
// documentation are those of the authors and should not be
33
// interpreted as representing official policies, either expressed
34
// or implied, of GRNET S.A.
35
// </copyright>
36
// -----------------------------------------------------------------------
37

  
38

  
39
using System.Diagnostics.Contracts;
40
using System.IO;
41
using System.Security.Permissions;
42
using System;
43

  
44
namespace Pithos.Core.Agents
45
{
46
    
47

  
48
    public delegate void MovedEventHandler(object sender, MovedEventArgs e);
49

  
50
    public class MovedEventArgs : FileSystemEventArgs
51
    {
52
        private readonly string _oldName;
53
        private readonly string _oldFullPath;
54

  
55
        public string OldFullPath
56
        {
57
            get
58
            {
59
                new FileIOPermission(FileIOPermissionAccess.Read, Path.GetPathRoot(_oldFullPath)).Demand();
60
                return _oldFullPath;
61
            }
62
        }
63

  
64
        public string OldName
65
        {
66
            get { return _oldName; }
67
        }
68

  
69
        public MovedEventArgs(string newDirectory, string newName, string oldDirectory, string oldName)
70
            : base(WatcherChangeTypes.Renamed, newDirectory, newName)
71
        {
72
            if (String.IsNullOrWhiteSpace(oldDirectory))
73
                throw new ArgumentNullException("oldDirectory");
74
            if (String.IsNullOrWhiteSpace(oldName))
75
                throw new ArgumentNullException("oldName");
76
            Contract.EndContractBlock();
77

  
78
            _oldName = oldName;
79
            _oldFullPath = Path.Combine(oldDirectory, oldName);
80
        }
81
    }
82
}
b/trunk/Pithos.Core/Pithos.Core.csproj
390 390
    <Compile Include="Agents\CollectionExtensions.cs" />
391 391
    <Compile Include="Agents\FileAgent.cs" />
392 392
    <Compile Include="Agents\FileInfoExtensions.cs" />
393
    <Compile Include="Agents\FileSystemWatcherAdapter.cs" />
394
    <Compile Include="Agents\MovedEventArgs.cs" />
393 395
    <Compile Include="Agents\NetworkAgent.cs" />
394 396
    <Compile Include="Agents\WorkflowAgent.cs" />
395 397
    <Compile Include="DynamicDictionary.cs" />

Also available in: Unified diff