2 using System.Collections.Generic;
3 using System.ComponentModel.Composition;
4 using System.Diagnostics;
5 using System.Diagnostics.Contracts;
9 using Pithos.Interfaces;
12 namespace Pithos.Core.Agents
15 public class FileAgent
17 Agent<WorkflowState> _agent;
18 private FileSystemWatcher _watcher;
21 public IStatusKeeper StatusKeeper { get; set; }
23 public IPithosWorkflow Workflow { get; set; }
25 public WorkflowAgent WorkflowAgent { get; set; }
27 public string RootPath { get; private set; }
29 public void Start(string rootPath)
32 _watcher = new FileSystemWatcher(rootPath);
33 _watcher.Changed += OnFileEvent;
34 _watcher.Created += OnFileEvent;
35 _watcher.Deleted += OnFileEvent;
36 _watcher.Renamed += OnRenameEvent;
37 _watcher.EnableRaisingEvents = true;
40 _agent = Agent<WorkflowState>.Start(inbox =>
45 var message = inbox.Receive();
46 var process = message.ContinueWith(t =>
53 process.ContinueWith(t =>
58 var ex = t.Exception.InnerException;
59 if (ex is OperationCanceledException)
61 Trace.TraceError("[ERROR] File Event Processing:\r{0}", ex);
72 get { return _watcher == null || !_watcher.EnableRaisingEvents; }
76 _watcher.EnableRaisingEvents = !value;
80 public string FragmentsPath { get; set; }
82 public void Post(WorkflowState workflowState)
84 _agent.Post(workflowState);
91 _watcher.Changed -= OnFileEvent;
92 _watcher.Created -= OnFileEvent;
93 _watcher.Deleted -= OnFileEvent;
94 _watcher.Renamed -= OnRenameEvent;
102 // Enumerate all files in the Pithos directory except those in the Fragment folder
103 // and files with a .ignore extension
104 public IEnumerable<string> EnumerateFiles(string searchPattern="*")
106 var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
107 where !Ignore(filePath)
109 return monitoredFiles;
112 public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
114 var rootDir = new DirectoryInfo(RootPath);
115 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
116 where !Ignore(file.FullName)
118 return monitoredFiles;
121 public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
123 var rootDir = new DirectoryInfo(RootPath);
124 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
125 where !Ignore(file.FullName)
126 select file.AsRelativeUrlTo(RootPath);
127 return monitoredFiles;
133 private bool Ignore(string filePath)
135 if (filePath.StartsWith(FragmentsPath))
140 //Post a Change message for all events except rename
141 void OnFileEvent(object sender, FileSystemEventArgs e)
143 //Ignore events that affect the Fragments folder
144 var filePath = e.FullPath;
145 if (Ignore(filePath))
147 _agent.Post(new WorkflowState { Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
151 //Post a Change message for renames containing the old and new names
152 void OnRenameEvent(object sender, RenamedEventArgs e)
154 var oldFullPath = e.OldFullPath;
155 var fullPath = e.FullPath;
156 if (Ignore(oldFullPath) || Ignore(fullPath))
159 _agent.Post(new WorkflowState
161 OldPath = oldFullPath,
162 OldFileName = e.OldName,
165 TriggeringChange = e.ChangeType
170 private void Process(WorkflowState state)
172 Debug.Assert(!Ignore(state.Path));
174 var networkState = NetworkGate.GetNetworkState(state.Path);
175 //Skip if the file is already being downloaded or uploaded and
176 //the change is create or modify
177 if (networkState != NetworkOperation.None &&
179 state.TriggeringChange == WatcherChangeTypes.Created ||
180 state.TriggeringChange == WatcherChangeTypes.Changed
186 UpdateFileStatus(state);
187 UpdateOverlayStatus(state);
188 UpdateFileChecksum(state);
189 WorkflowAgent.Post(state);
191 catch (IOException exc)
193 Trace.TraceWarning("File access error occured, retrying {0}\n{1}", state.Path, exc);
196 catch (Exception exc)
198 Trace.TraceWarning("Error occured while indexing{0. The file will be skipped}\n{1}", state.Path, exc);
202 private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
204 {WatcherChangeTypes.Created,FileStatus.Created},
205 {WatcherChangeTypes.Changed,FileStatus.Modified},
206 {WatcherChangeTypes.Deleted,FileStatus.Deleted},
207 {WatcherChangeTypes.Renamed,FileStatus.Renamed}
210 private WorkflowState UpdateFileStatus(WorkflowState state)
212 Debug.Assert(!state.Path.Contains("fragments"));
213 Debug.Assert(!state.Path.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase));
215 string path = state.Path;
216 FileStatus status = _statusDict[state.TriggeringChange];
217 var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
218 if (status == oldStatus)
220 state.Status = status;
224 if (state.Status == FileStatus.Renamed)
225 Workflow.ClearFileStatus(path);
227 state.Status = Workflow.SetFileStatus(path, status);
231 private WorkflowState UpdateOverlayStatus(WorkflowState state)
236 switch (state.Status)
238 case FileStatus.Created:
239 case FileStatus.Modified:
240 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
242 case FileStatus.Deleted:
243 this.StatusKeeper.RemoveFileOverlayStatus(state.Path);
245 case FileStatus.Renamed:
246 this.StatusKeeper.RemoveFileOverlayStatus(state.OldPath);
247 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
249 case FileStatus.Unchanged:
250 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
254 if (state.Status == FileStatus.Deleted)
255 NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
257 NativeMethods.RaiseChangeNotification(state.Path);
262 private WorkflowState UpdateFileChecksum(WorkflowState state)
267 if (state.Status == FileStatus.Deleted)
270 var path = state.Path;
271 //Skip calculation for folders
272 if (Directory.Exists(path))
275 string hash = Signature.CalculateMD5(path);
277 StatusKeeper.UpdateFileChecksum(path, hash);
283 //Does the file exist in the container's local folder?
284 public bool Exists(string relativePath)
286 if (String.IsNullOrWhiteSpace(relativePath))
287 throw new ArgumentNullException("relativePath");
288 //A RootPath must be set before calling this method
289 if (String.IsNullOrWhiteSpace(RootPath))
290 throw new InvalidOperationException("RootPath was not set");
291 Contract.EndContractBlock();
292 //Create the absolute path by combining the RootPath with the relativePath
293 var absolutePath=Path.Combine(RootPath, relativePath);
294 //Is this a valid file?
295 if (File.Exists(absolutePath))
298 if (Directory.Exists(RootPath))
300 //Fail if it is neither
304 public FileInfo GetFileInfo(string relativePath)
306 if (String.IsNullOrWhiteSpace(relativePath))
307 throw new ArgumentNullException("relativePath");
308 //A RootPath must be set before calling this method
309 if (String.IsNullOrWhiteSpace(RootPath))
310 throw new InvalidOperationException("RootPath was not set");
311 Contract.EndContractBlock();
313 var absolutePath = Path.Combine(RootPath, relativePath);
314 Debug.Assert(File.Exists(absolutePath));
316 return new FileInfo(absolutePath);