2 using System.Collections.Generic;
3 using System.ComponentModel.Composition;
4 using System.Diagnostics;
5 using System.Diagnostics.Contracts;
9 using System.Threading.Tasks;
10 using Pithos.Interfaces;
13 namespace Pithos.Core.Agents
16 public class FileAgent
18 Agent<WorkflowState> _agent;
19 private FileSystemWatcher _watcher;
22 public IStatusKeeper StatusKeeper { get; set; }
24 public IPithosWorkflow Workflow { get; set; }
26 public WorkflowAgent WorkflowAgent { get; set; }
28 public string RootPath { get; private set; }
30 public void Start(string rootPath)
33 _watcher = new FileSystemWatcher(rootPath);
34 _watcher.Changed += OnFileEvent;
35 _watcher.Created += OnFileEvent;
36 _watcher.Deleted += OnFileEvent;
37 _watcher.Renamed += OnRenameEvent;
38 _watcher.EnableRaisingEvents = true;
41 _agent = Agent<WorkflowState>.Start(inbox =>
46 var message = inbox.Receive();
47 var process=message.Then(Process,inbox.CancellationToken);
49 inbox.LoopAsync(process,loop,ex=>
50 Trace.TraceError("[ERROR] File Event Processing:\r{0}", ex));
56 private Task<object> Process(WorkflowState state)
58 Debug.Assert(!Ignore(state.Path));
60 var networkState = NetworkGate.GetNetworkState(state.Path);
61 //Skip if the file is already being downloaded or uploaded and
62 //the change is create or modify
63 if (networkState != NetworkOperation.None &&
65 state.TriggeringChange == WatcherChangeTypes.Created ||
66 state.TriggeringChange == WatcherChangeTypes.Changed
68 return CompletedTask<object>.Default;
72 UpdateFileStatus(state);
73 UpdateOverlayStatus(state);
74 UpdateFileChecksum(state);
75 WorkflowAgent.Post(state);
77 catch (IOException exc)
79 Trace.TraceWarning("File access error occured, retrying {0}\n{1}", state.Path, exc);
84 Trace.TraceWarning("Error occured while indexing{0. The file will be skipped}\n{1}", state.Path, exc);
86 return CompletedTask<object>.Default;
91 private Task Process(Task<WorkflowState> action)
93 return action.ContinueWith(t => Process(t.Result));
100 get { return _watcher == null || !_watcher.EnableRaisingEvents; }
103 if (_watcher != null)
104 _watcher.EnableRaisingEvents = !value;
108 public string FragmentsPath { get; set; }
110 public void Post(WorkflowState workflowState)
112 _agent.Post(workflowState);
117 if (_watcher != null)
119 _watcher.Changed -= OnFileEvent;
120 _watcher.Created -= OnFileEvent;
121 _watcher.Deleted -= OnFileEvent;
122 _watcher.Renamed -= OnRenameEvent;
130 // Enumerate all files in the Pithos directory except those in the Fragment folder
131 // and files with a .ignore extension
132 public IEnumerable<string> EnumerateFiles(string searchPattern="*")
134 var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
135 where !Ignore(filePath)
137 return monitoredFiles;
140 public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
142 var rootDir = new DirectoryInfo(RootPath);
143 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
144 where !Ignore(file.FullName)
146 return monitoredFiles;
149 public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
151 var rootDir = new DirectoryInfo(RootPath);
152 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
153 where !Ignore(file.FullName)
154 select file.AsRelativeUrlTo(RootPath);
155 return monitoredFiles;
161 private bool Ignore(string filePath)
163 if (filePath.StartsWith(FragmentsPath))
168 //Post a Change message for all events except rename
169 void OnFileEvent(object sender, FileSystemEventArgs e)
171 //Ignore events that affect the Fragments folder
172 var filePath = e.FullPath;
173 if (Ignore(filePath))
175 _agent.Post(new WorkflowState { Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
179 //Post a Change message for renames containing the old and new names
180 void OnRenameEvent(object sender, RenamedEventArgs e)
182 var oldFullPath = e.OldFullPath;
183 var fullPath = e.FullPath;
184 if (Ignore(oldFullPath) || Ignore(fullPath))
187 _agent.Post(new WorkflowState
189 OldPath = oldFullPath,
190 OldFileName = e.OldName,
193 TriggeringChange = e.ChangeType
199 private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
201 {WatcherChangeTypes.Created,FileStatus.Created},
202 {WatcherChangeTypes.Changed,FileStatus.Modified},
203 {WatcherChangeTypes.Deleted,FileStatus.Deleted},
204 {WatcherChangeTypes.Renamed,FileStatus.Renamed}
207 private WorkflowState UpdateFileStatus(WorkflowState state)
209 Debug.Assert(!state.Path.Contains("fragments"));
210 Debug.Assert(!state.Path.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase));
212 string path = state.Path;
213 FileStatus status = _statusDict[state.TriggeringChange];
214 var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
215 if (status == oldStatus)
217 state.Status = status;
221 if (state.Status == FileStatus.Renamed)
222 Workflow.ClearFileStatus(path);
224 state.Status = Workflow.SetFileStatus(path, status);
228 private WorkflowState UpdateOverlayStatus(WorkflowState state)
233 switch (state.Status)
235 case FileStatus.Created:
236 case FileStatus.Modified:
237 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
239 case FileStatus.Deleted:
240 this.StatusKeeper.RemoveFileOverlayStatus(state.Path);
242 case FileStatus.Renamed:
243 this.StatusKeeper.RemoveFileOverlayStatus(state.OldPath);
244 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
246 case FileStatus.Unchanged:
247 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
251 if (state.Status == FileStatus.Deleted)
252 NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
254 NativeMethods.RaiseChangeNotification(state.Path);
259 private WorkflowState UpdateFileChecksum(WorkflowState state)
264 if (state.Status == FileStatus.Deleted)
267 var path = state.Path;
268 //Skip calculation for folders
269 if (Directory.Exists(path))
272 string hash = Signature.CalculateMD5(path);
274 StatusKeeper.UpdateFileChecksum(path, hash);
280 //Does the file exist in the container's local folder?
281 public bool Exists(string relativePath)
283 if (String.IsNullOrWhiteSpace(relativePath))
284 throw new ArgumentNullException("relativePath");
285 //A RootPath must be set before calling this method
286 if (String.IsNullOrWhiteSpace(RootPath))
287 throw new InvalidOperationException("RootPath was not set");
288 Contract.EndContractBlock();
289 //Create the absolute path by combining the RootPath with the relativePath
290 var absolutePath=Path.Combine(RootPath, relativePath);
291 //Is this a valid file?
292 if (File.Exists(absolutePath))
295 if (Directory.Exists(absolutePath))
297 //Fail if it is neither
301 public FileInfo GetFileInfo(string relativePath)
303 if (String.IsNullOrWhiteSpace(relativePath))
304 throw new ArgumentNullException("relativePath");
305 //A RootPath must be set before calling this method
306 if (String.IsNullOrWhiteSpace(RootPath))
307 throw new InvalidOperationException("RootPath was not set");
308 Contract.EndContractBlock();
310 var absolutePath = Path.Combine(RootPath, relativePath);
311 Debug.Assert(File.Exists(absolutePath),String.Format("Path {0} doesn't exist",absolutePath));
313 return new FileInfo(absolutePath);