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;
15 namespace Pithos.Core.Agents
18 public class FileAgent
20 Agent<WorkflowState> _agent;
21 private FileSystemWatcher _watcher;
24 public IStatusKeeper StatusKeeper { get; set; }
26 public IPithosWorkflow Workflow { get; set; }
28 public WorkflowAgent WorkflowAgent { get; set; }
30 private AccountInfo AccountInfo { get; set; }
32 private string RootPath { get; set; }
34 private static readonly ILog Log = LogManager.GetLogger("FileAgent");
36 public void Start(AccountInfo accountInfo,string rootPath)
38 if (accountInfo==null)
39 throw new ArgumentNullException("accountInfo");
40 if (String.IsNullOrWhiteSpace(rootPath))
41 throw new ArgumentNullException("rootPath");
42 if (!Path.IsPathRooted(rootPath))
43 throw new ArgumentException("rootPath must be an absolute path","rootPath");
44 Contract.EndContractBlock();
46 AccountInfo = accountInfo;
48 _watcher = new FileSystemWatcher(rootPath);
49 _watcher.IncludeSubdirectories = true;
50 _watcher.Changed += OnFileEvent;
51 _watcher.Created += OnFileEvent;
52 _watcher.Deleted += OnFileEvent;
53 _watcher.Renamed += OnRenameEvent;
54 _watcher.EnableRaisingEvents = true;
57 _agent = Agent<WorkflowState>.Start(inbox =>
62 var message = inbox.Receive();
63 var process=message.Then(Process,inbox.CancellationToken);
65 inbox.LoopAsync(process,loop,ex=>
66 Log.ErrorFormat("[ERROR] File Event Processing:\r{0}", ex));
72 private Task<object> Process(WorkflowState state)
75 throw new ArgumentNullException("state");
76 Contract.EndContractBlock();
78 Debug.Assert(!Ignore(state.Path));
80 var networkState = NetworkGate.GetNetworkState(state.Path);
81 //Skip if the file is already being downloaded or uploaded and
82 //the change is create or modify
83 if (networkState != NetworkOperation.None &&
85 state.TriggeringChange == WatcherChangeTypes.Created ||
86 state.TriggeringChange == WatcherChangeTypes.Changed
88 return CompletedTask<object>.Default;
92 UpdateFileStatus(state);
93 UpdateOverlayStatus(state);
94 UpdateFileChecksum(state);
95 WorkflowAgent.Post(state);
97 catch (IOException exc)
99 if (File.Exists(state.Path))
101 Log.WarnFormat("File access error occured, retrying {0}\n{1}", state.Path, exc);
106 Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);
109 catch (Exception exc)
111 Log.WarnFormat("Error occured while indexing{0. The file will be skipped}\n{1}", state.Path, exc);
113 return CompletedTask<object>.Default;
118 private Task Process(Task<WorkflowState> action)
120 return action.ContinueWith(t => Process(t.Result));
127 get { return _watcher == null || !_watcher.EnableRaisingEvents; }
130 if (_watcher != null)
131 _watcher.EnableRaisingEvents = !value;
135 public string FragmentsPath { get; set; }
137 public void Post(WorkflowState workflowState)
139 if (workflowState == null)
140 throw new ArgumentNullException("workflowState");
141 Contract.EndContractBlock();
143 _agent.Post(workflowState);
148 if (_watcher != null)
150 _watcher.Changed -= OnFileEvent;
151 _watcher.Created -= OnFileEvent;
152 _watcher.Deleted -= OnFileEvent;
153 _watcher.Renamed -= OnRenameEvent;
162 // Enumerate all files in the Pithos directory except those in the Fragment folder
163 // and files with a .ignore extension
164 public IEnumerable<string> EnumerateFiles(string searchPattern="*")
166 var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
167 where !Ignore(filePath)
169 return monitoredFiles;
172 public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
174 var rootDir = new DirectoryInfo(RootPath);
175 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
176 where !Ignore(file.FullName)
178 return monitoredFiles;
181 public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
183 var rootDir = new DirectoryInfo(RootPath);
184 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
185 where !Ignore(file.FullName)
186 select file.AsRelativeUrlTo(RootPath);
187 return monitoredFiles;
193 private bool Ignore(string filePath)
195 if (filePath.StartsWith(FragmentsPath))
197 if (_ignoreFiles.ContainsKey(filePath.ToLower()))
202 //Post a Change message for all events except rename
203 void OnFileEvent(object sender, FileSystemEventArgs e)
205 //Ignore events that affect the Fragments folder
206 var filePath = e.FullPath;
207 if (Ignore(filePath))
209 _agent.Post(new WorkflowState(AccountInfo) { Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
213 //Post a Change message for renames containing the old and new names
214 void OnRenameEvent(object sender, RenamedEventArgs e)
216 var oldFullPath = e.OldFullPath;
217 var fullPath = e.FullPath;
218 if (Ignore(oldFullPath) || Ignore(fullPath))
221 _agent.Post(new WorkflowState(AccountInfo)
223 OldPath = oldFullPath,
224 OldFileName = e.OldName,
227 TriggeringChange = e.ChangeType
233 private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
235 {WatcherChangeTypes.Created,FileStatus.Created},
236 {WatcherChangeTypes.Changed,FileStatus.Modified},
237 {WatcherChangeTypes.Deleted,FileStatus.Deleted},
238 {WatcherChangeTypes.Renamed,FileStatus.Renamed}
241 private Dictionary<string,string> _ignoreFiles=new Dictionary<string, string>();
243 private WorkflowState UpdateFileStatus(WorkflowState state)
246 throw new ArgumentNullException("state");
247 if (String.IsNullOrWhiteSpace(state.Path))
248 throw new ArgumentException("The state's Path can't be empty","state");
249 Contract.EndContractBlock();
251 var path = state.Path;
252 var status = _statusDict[state.TriggeringChange];
253 var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
254 if (status == oldStatus)
256 state.Status = status;
260 if (state.Status == FileStatus.Renamed)
261 Workflow.ClearFileStatus(path);
263 state.Status = Workflow.SetFileStatus(path, status);
267 private WorkflowState UpdateOverlayStatus(WorkflowState state)
270 throw new ArgumentNullException("state");
271 Contract.EndContractBlock();
276 switch (state.Status)
278 case FileStatus.Created:
279 case FileStatus.Modified:
280 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
282 case FileStatus.Deleted:
283 //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
285 case FileStatus.Renamed:
286 this.StatusKeeper.ClearFileStatus(state.OldPath);
287 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
289 case FileStatus.Unchanged:
290 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
294 if (state.Status == FileStatus.Deleted)
295 NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
297 NativeMethods.RaiseChangeNotification(state.Path);
302 private WorkflowState UpdateFileChecksum(WorkflowState state)
307 if (state.Status == FileStatus.Deleted)
310 var path = state.Path;
311 //Skip calculation for folders
312 if (Directory.Exists(path))
315 var info = new FileInfo(path);
316 string hash = info.CalculateHash(StatusKeeper.BlockSize,StatusKeeper.BlockHash);
317 StatusKeeper.UpdateFileChecksum(path, hash);
323 //Does the file exist in the container's local folder?
324 public bool Exists(string relativePath)
326 if (String.IsNullOrWhiteSpace(relativePath))
327 throw new ArgumentNullException("relativePath");
328 //A RootPath must be set before calling this method
329 if (String.IsNullOrWhiteSpace(RootPath))
330 throw new InvalidOperationException("RootPath was not set");
331 Contract.EndContractBlock();
332 //Create the absolute path by combining the RootPath with the relativePath
333 var absolutePath=Path.Combine(RootPath, relativePath);
334 //Is this a valid file?
335 if (File.Exists(absolutePath))
338 if (Directory.Exists(absolutePath))
340 //Fail if it is neither
344 public FileInfo GetFileInfo(string relativePath)
346 if (String.IsNullOrWhiteSpace(relativePath))
347 throw new ArgumentNullException("relativePath");
348 //A RootPath must be set before calling this method
349 if (String.IsNullOrWhiteSpace(RootPath))
350 throw new InvalidOperationException("RootPath was not set");
351 Contract.EndContractBlock();
353 var absolutePath = Path.Combine(RootPath, relativePath);
354 // Debug.Assert(File.Exists(absolutePath),String.Format("Path {0} doesn't exist",absolutePath));
356 return new FileInfo(absolutePath);
360 public void Delete(string relativePath)
362 var absolutePath = Path.Combine(RootPath, relativePath);
363 if (File.Exists(absolutePath))
365 File.Delete(absolutePath);
366 _ignoreFiles[absolutePath.ToLower()] = absolutePath.ToLower();
368 StatusKeeper.ClearFileStatus(absolutePath);