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 if (File.Exists(state.Path))
81 Trace.TraceWarning("File access error occured, retrying {0}\n{1}", state.Path, exc);
86 Trace.TraceWarning("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);
91 Trace.TraceWarning("Error occured while indexing{0. The file will be skipped}\n{1}", state.Path, exc);
93 return CompletedTask<object>.Default;
98 private Task Process(Task<WorkflowState> action)
100 return action.ContinueWith(t => Process(t.Result));
107 get { return _watcher == null || !_watcher.EnableRaisingEvents; }
110 if (_watcher != null)
111 _watcher.EnableRaisingEvents = !value;
115 public string FragmentsPath { get; set; }
117 public void Post(WorkflowState workflowState)
119 _agent.Post(workflowState);
124 if (_watcher != null)
126 _watcher.Changed -= OnFileEvent;
127 _watcher.Created -= OnFileEvent;
128 _watcher.Deleted -= OnFileEvent;
129 _watcher.Renamed -= OnRenameEvent;
137 // Enumerate all files in the Pithos directory except those in the Fragment folder
138 // and files with a .ignore extension
139 public IEnumerable<string> EnumerateFiles(string searchPattern="*")
141 var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
142 where !Ignore(filePath)
144 return monitoredFiles;
147 public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
149 var rootDir = new DirectoryInfo(RootPath);
150 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
151 where !Ignore(file.FullName)
153 return monitoredFiles;
156 public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
158 var rootDir = new DirectoryInfo(RootPath);
159 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
160 where !Ignore(file.FullName)
161 select file.AsRelativeUrlTo(RootPath);
162 return monitoredFiles;
168 private bool Ignore(string filePath)
170 if (filePath.StartsWith(FragmentsPath))
172 if (_ignoreFiles.ContainsKey(filePath.ToLower()))
177 //Post a Change message for all events except rename
178 void OnFileEvent(object sender, FileSystemEventArgs e)
180 //Ignore events that affect the Fragments folder
181 var filePath = e.FullPath;
182 if (Ignore(filePath))
184 _agent.Post(new WorkflowState { Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
188 //Post a Change message for renames containing the old and new names
189 void OnRenameEvent(object sender, RenamedEventArgs e)
191 var oldFullPath = e.OldFullPath;
192 var fullPath = e.FullPath;
193 if (Ignore(oldFullPath) || Ignore(fullPath))
196 _agent.Post(new WorkflowState
198 OldPath = oldFullPath,
199 OldFileName = e.OldName,
202 TriggeringChange = e.ChangeType
208 private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
210 {WatcherChangeTypes.Created,FileStatus.Created},
211 {WatcherChangeTypes.Changed,FileStatus.Modified},
212 {WatcherChangeTypes.Deleted,FileStatus.Deleted},
213 {WatcherChangeTypes.Renamed,FileStatus.Renamed}
216 private Dictionary<string,string> _ignoreFiles=new Dictionary<string, string>();
218 private WorkflowState UpdateFileStatus(WorkflowState state)
220 Debug.Assert(!state.Path.Contains("fragments"));
221 Debug.Assert(!state.Path.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase));
223 string path = state.Path;
224 FileStatus status = _statusDict[state.TriggeringChange];
225 var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
226 if (status == oldStatus)
228 state.Status = status;
232 if (state.Status == FileStatus.Renamed)
233 Workflow.ClearFileStatus(path);
235 state.Status = Workflow.SetFileStatus(path, status);
239 private WorkflowState UpdateOverlayStatus(WorkflowState state)
244 switch (state.Status)
246 case FileStatus.Created:
247 case FileStatus.Modified:
248 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
250 case FileStatus.Deleted:
251 //this.StatusKeeper.RemoveFileOverlayStatus(state.Path);
253 case FileStatus.Renamed:
254 this.StatusKeeper.RemoveFileOverlayStatus(state.OldPath);
255 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
257 case FileStatus.Unchanged:
258 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
262 if (state.Status == FileStatus.Deleted)
263 NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
265 NativeMethods.RaiseChangeNotification(state.Path);
270 private WorkflowState UpdateFileChecksum(WorkflowState state)
275 if (state.Status == FileStatus.Deleted)
278 var path = state.Path;
279 //Skip calculation for folders
280 if (Directory.Exists(path))
283 var info = new FileInfo(path);
284 string hash = info.CalculateHash(StatusKeeper.BlockSize,StatusKeeper.BlockHash);
285 StatusKeeper.UpdateFileChecksum(path, hash);
291 //Does the file exist in the container's local folder?
292 public bool Exists(string relativePath)
294 if (String.IsNullOrWhiteSpace(relativePath))
295 throw new ArgumentNullException("relativePath");
296 //A RootPath must be set before calling this method
297 if (String.IsNullOrWhiteSpace(RootPath))
298 throw new InvalidOperationException("RootPath was not set");
299 Contract.EndContractBlock();
300 //Create the absolute path by combining the RootPath with the relativePath
301 var absolutePath=Path.Combine(RootPath, relativePath);
302 //Is this a valid file?
303 if (File.Exists(absolutePath))
306 if (Directory.Exists(absolutePath))
308 //Fail if it is neither
312 public FileInfo GetFileInfo(string relativePath)
314 if (String.IsNullOrWhiteSpace(relativePath))
315 throw new ArgumentNullException("relativePath");
316 //A RootPath must be set before calling this method
317 if (String.IsNullOrWhiteSpace(RootPath))
318 throw new InvalidOperationException("RootPath was not set");
319 Contract.EndContractBlock();
321 var absolutePath = Path.Combine(RootPath, relativePath);
322 Debug.Assert(File.Exists(absolutePath),String.Format("Path {0} doesn't exist",absolutePath));
324 return new FileInfo(absolutePath);
328 public void Delete(string relativePath)
330 var absolutePath = Path.Combine(RootPath, relativePath);
331 if (File.Exists(absolutePath))
333 File.Delete(absolutePath);
334 _ignoreFiles[absolutePath.ToLower()] = absolutePath.ToLower();
336 StatusKeeper.ClearFileStatus(absolutePath);