Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / FileAgent.cs @ 1caef52e

History | View | Annotate | Download (9 kB)

1
using System;
2
using System.Collections.Generic;
3
using System.ComponentModel.Composition;
4
using System.Diagnostics;
5
using System.IO;
6
using System.Linq;
7
using System.Text;
8
using Pithos.Interfaces;
9
using Pithos.Network;
10

    
11
namespace Pithos.Core.Agents
12
{
13
    [Export]
14
    public class FileAgent
15
    {
16
        Agent<WorkflowState> _agent;
17
        private FileSystemWatcher _watcher;
18

    
19
        [Import]
20
        public IStatusKeeper StatusKeeper { get; set; }
21
        [Import]
22
        public IPithosWorkflow Workflow { get; set; }
23
        [Import]
24
        public WorkflowAgent WorkflowAgent { get; set; }
25

    
26
        public string RootPath { get; private set; }
27

    
28
        public void Start(string rootPath)
29
        {
30
            RootPath = rootPath;
31
            _watcher = new FileSystemWatcher(rootPath);
32
            _watcher.Changed += OnFileEvent;
33
            _watcher.Created += OnFileEvent;
34
            _watcher.Deleted += OnFileEvent;
35
            _watcher.Renamed += OnRenameEvent;
36
            _watcher.EnableRaisingEvents = true;
37

    
38

    
39
            _agent = Agent<WorkflowState>.Start(inbox =>
40
            {
41
                Action loop = null;
42
                loop = () =>
43
                {
44
                    var message = inbox.Receive();
45
                    var process = message.ContinueWith(t =>
46
                    {
47
                        var state = t.Result;
48
                        Process(state);
49
                        inbox.DoAsync(loop);
50
                    });
51

    
52
                    process.ContinueWith(t =>
53
                    {
54
                        inbox.DoAsync(loop);
55
                        if (t.IsFaulted)
56
                        {
57
                            var ex = t.Exception.InnerException;
58
                            if (ex is OperationCanceledException)
59
                                inbox.Stop();
60
                            Trace.TraceError("[ERROR] File Event Processing:\r{0}", ex);
61
                        }
62
                    });
63

    
64
                };
65
                loop();
66
            });
67
        }
68

    
69
        public bool Pause
70
        {
71
            get { return _watcher == null || !_watcher.EnableRaisingEvents; }
72
            set
73
            {
74
                if (_watcher != null)
75
                    _watcher.EnableRaisingEvents = !value;                
76
            }
77
        }
78

    
79
        public string FragmentsPath { get; set; }
80

    
81
        public void Post(WorkflowState workflowState)
82
        {
83
            _agent.Post(workflowState);
84
        }
85

    
86
        public void Stop()
87
        {
88
            if (_watcher != null)
89
            {
90
                _watcher.Changed -= OnFileEvent;
91
                _watcher.Created -= OnFileEvent;
92
                _watcher.Deleted -= OnFileEvent;
93
                _watcher.Renamed -= OnRenameEvent;
94
                _watcher.Dispose();
95
            }
96
            _watcher = null;
97

    
98
            _agent.Stop();
99
        }
100

    
101
        // Enumerate all files in the Pithos directory except those in the Fragment folder
102
        // and files with a .ignore extension
103
        public IEnumerable<string> EnumerateFiles(string searchPattern="*")
104
        {
105
            var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
106
                                 where !Ignore(filePath)
107
                                 select filePath;
108
            return monitoredFiles;
109
        }
110

    
111
        public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
112
        {
113
            var rootDir = new DirectoryInfo(RootPath);
114
            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
115
                                 where !Ignore(file.FullName)
116
                                 select file;
117
            return monitoredFiles;
118
        }                
119

    
120
        public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
121
        {
122
            var rootDir = new DirectoryInfo(RootPath);
123
            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
124
                                 where !Ignore(file.FullName)
125
                                 select file.AsRelativeUrlTo(RootPath);
126
            return monitoredFiles;
127
        }                
128

    
129

    
130
        
131

    
132
        private bool Ignore(string filePath)
133
        {
134
            if (filePath.StartsWith(FragmentsPath))
135
                return true;
136
            return false;
137
        }
138

    
139
        //Post a Change message for all events except rename
140
        void OnFileEvent(object sender, FileSystemEventArgs e)
141
        {
142
            //Ignore events that affect the Fragments folder
143
            var filePath = e.FullPath;
144
            if (Ignore(filePath)) 
145
                return;
146
            _agent.Post(new WorkflowState { Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
147
        }
148

    
149

    
150
        //Post a Change message for renames containing the old and new names
151
        void OnRenameEvent(object sender, RenamedEventArgs e)
152
        {
153
            var oldFullPath = e.OldFullPath;
154
            var fullPath = e.FullPath;
155
            if (Ignore(oldFullPath) || Ignore(fullPath))
156
                return;
157

    
158
            _agent.Post(new WorkflowState
159
            {
160
                OldPath = oldFullPath,
161
                OldFileName = e.OldName,
162
                Path = fullPath,
163
                FileName = e.Name,
164
                TriggeringChange = e.ChangeType
165
            });
166
        }
167

    
168

    
169
        private void Process(WorkflowState state)
170
        {
171
            Debug.Assert(!Ignore(state.Path));            
172

    
173
            var networkState = NetworkGate.GetNetworkState(state.Path);
174
            //Skip if the file is already being downloaded or uploaded and 
175
            //the change is create or modify
176
            if (networkState != NetworkOperation.None &&
177
                (
178
                    state.TriggeringChange == WatcherChangeTypes.Created ||
179
                    state.TriggeringChange == WatcherChangeTypes.Changed
180
                ))
181
                return;
182
            UpdateFileStatus(state);
183
            UpdateOverlayStatus(state);
184
            UpdateFileChecksum(state);
185
            WorkflowAgent.Post(state);
186
        }
187

    
188
        private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
189
        {
190
            {WatcherChangeTypes.Created,FileStatus.Created},
191
            {WatcherChangeTypes.Changed,FileStatus.Modified},
192
            {WatcherChangeTypes.Deleted,FileStatus.Deleted},
193
            {WatcherChangeTypes.Renamed,FileStatus.Renamed}
194
        };
195

    
196
        private WorkflowState UpdateFileStatus(WorkflowState state)
197
        {
198
            Debug.Assert(!state.Path.Contains("fragments"));
199
            Debug.Assert(!state.Path.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase));
200

    
201
            string path = state.Path;
202
            FileStatus status = _statusDict[state.TriggeringChange];
203
            var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
204
            if (status == oldStatus)
205
            {
206
                state.Status = status;
207
                state.Skip = true;
208
                return state;
209
            }
210
            if (state.Status == FileStatus.Renamed)
211
                Workflow.ClearFileStatus(path);
212

    
213
            state.Status = Workflow.SetFileStatus(path, status);
214
            return state;
215
        }
216

    
217
        private WorkflowState UpdateOverlayStatus(WorkflowState state)
218
        {
219
            if (state.Skip)
220
                return state;
221

    
222
            switch (state.Status)
223
            {
224
                case FileStatus.Created:
225
                case FileStatus.Modified:
226
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
227
                    break;
228
                case FileStatus.Deleted:
229
                    this.StatusKeeper.RemoveFileOverlayStatus(state.Path);
230
                    break;
231
                case FileStatus.Renamed:
232
                    this.StatusKeeper.RemoveFileOverlayStatus(state.OldPath);
233
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
234
                    break;
235
                case FileStatus.Unchanged:
236
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
237
                    break;
238
            }
239

    
240
            if (state.Status == FileStatus.Deleted)
241
                NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
242
            else
243
                NativeMethods.RaiseChangeNotification(state.Path);
244
            return state;
245
        }
246

    
247

    
248
        private WorkflowState UpdateFileChecksum(WorkflowState state)
249
        {
250
            if (state.Skip)
251
                return state;
252

    
253
            if (state.Status == FileStatus.Deleted)
254
                return state;
255

    
256
            var path = state.Path;
257
            //Skip calculation for folders
258
            if (Directory.Exists(path))
259
                return state;
260

    
261
            string hash = Signature.CalculateMD5(path);
262

    
263
            StatusKeeper.UpdateFileChecksum(path, hash);
264

    
265
            state.Hash = hash;
266
            return state;
267
        }
268

    
269
       
270

    
271

    
272
    }
273
}