Added treehash calculation
[pithos-ms-client] / trunk / Pithos.Core / Agents / FileAgent.cs
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 }