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 private List<string> _selectivePaths = new List<string>();
138 public List<string> SelectivePaths
140 get { return _selectivePaths; }
141 set { _selectivePaths = value; }
145 public void Post(WorkflowState workflowState)
147 if (workflowState == null)
148 throw new ArgumentNullException("workflowState");
149 Contract.EndContractBlock();
151 _agent.Post(workflowState);
156 if (_watcher != null)
158 _watcher.Changed -= OnFileEvent;
159 _watcher.Created -= OnFileEvent;
160 _watcher.Deleted -= OnFileEvent;
161 _watcher.Renamed -= OnRenameEvent;
170 // Enumerate all files in the Pithos directory except those in the Fragment folder
171 // and files with a .ignore extension
172 public IEnumerable<string> EnumerateFiles(string searchPattern="*")
174 var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
175 where !Ignore(filePath)
177 return monitoredFiles;
180 public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
182 var rootDir = new DirectoryInfo(RootPath);
183 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
184 where !Ignore(file.FullName)
186 return monitoredFiles;
189 public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
191 var rootDir = new DirectoryInfo(RootPath);
192 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
193 where !Ignore(file.FullName)
194 select file.AsRelativeUrlTo(RootPath);
195 return monitoredFiles;
201 private bool Ignore(string filePath)
203 if (filePath.StartsWith(FragmentsPath))
205 if (_ignoreFiles.ContainsKey(filePath.ToLower()))
210 //Post a Change message for all events except rename
211 void OnFileEvent(object sender, FileSystemEventArgs e)
213 //Ignore events that affect the Fragments folder
214 var filePath = e.FullPath;
215 if (Ignore(filePath))
218 _agent.Post(new WorkflowState{AccountInfo=AccountInfo, Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
222 //Post a Change message for renames containing the old and new names
223 void OnRenameEvent(object sender, RenamedEventArgs e)
225 var oldFullPath = e.OldFullPath;
226 var fullPath = e.FullPath;
227 if (Ignore(oldFullPath) || Ignore(fullPath))
230 _agent.Post(new WorkflowState
232 AccountInfo=AccountInfo,
233 OldPath = oldFullPath,
234 OldFileName = e.OldName,
237 TriggeringChange = e.ChangeType
243 private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
245 {WatcherChangeTypes.Created,FileStatus.Created},
246 {WatcherChangeTypes.Changed,FileStatus.Modified},
247 {WatcherChangeTypes.Deleted,FileStatus.Deleted},
248 {WatcherChangeTypes.Renamed,FileStatus.Renamed}
251 private Dictionary<string,string> _ignoreFiles=new Dictionary<string, string>();
253 private WorkflowState UpdateFileStatus(WorkflowState state)
256 throw new ArgumentNullException("state");
257 if (String.IsNullOrWhiteSpace(state.Path))
258 throw new ArgumentException("The state's Path can't be empty","state");
259 Contract.EndContractBlock();
261 var path = state.Path;
262 var status = _statusDict[state.TriggeringChange];
263 var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
264 if (status == oldStatus)
266 state.Status = status;
270 if (state.Status == FileStatus.Renamed)
271 Workflow.ClearFileStatus(path);
273 state.Status = Workflow.SetFileStatus(path, status);
277 private WorkflowState UpdateOverlayStatus(WorkflowState state)
280 throw new ArgumentNullException("state");
281 Contract.EndContractBlock();
286 switch (state.Status)
288 case FileStatus.Created:
289 case FileStatus.Modified:
290 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
292 case FileStatus.Deleted:
293 //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
295 case FileStatus.Renamed:
296 this.StatusKeeper.ClearFileStatus(state.OldPath);
297 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
299 case FileStatus.Unchanged:
300 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
304 if (state.Status == FileStatus.Deleted)
305 NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
307 NativeMethods.RaiseChangeNotification(state.Path);
312 private WorkflowState UpdateFileChecksum(WorkflowState state)
317 if (state.Status == FileStatus.Deleted)
320 var path = state.Path;
321 //Skip calculation for folders
322 if (Directory.Exists(path))
325 var info = new FileInfo(path);
326 string hash = info.CalculateHash(StatusKeeper.BlockSize,StatusKeeper.BlockHash);
327 StatusKeeper.UpdateFileChecksum(path, hash);
333 //Does the file exist in the container's local folder?
334 public bool Exists(string relativePath)
336 if (String.IsNullOrWhiteSpace(relativePath))
337 throw new ArgumentNullException("relativePath");
338 //A RootPath must be set before calling this method
339 if (String.IsNullOrWhiteSpace(RootPath))
340 throw new InvalidOperationException("RootPath was not set");
341 Contract.EndContractBlock();
342 //Create the absolute path by combining the RootPath with the relativePath
343 var absolutePath=Path.Combine(RootPath, relativePath);
344 //Is this a valid file?
345 if (File.Exists(absolutePath))
348 if (Directory.Exists(absolutePath))
350 //Fail if it is neither
354 public FileInfo GetFileInfo(string relativePath)
356 if (String.IsNullOrWhiteSpace(relativePath))
357 throw new ArgumentNullException("relativePath");
358 //A RootPath must be set before calling this method
359 if (String.IsNullOrWhiteSpace(RootPath))
360 throw new InvalidOperationException("RootPath was not set");
361 Contract.EndContractBlock();
363 var absolutePath = Path.Combine(RootPath, relativePath);
364 // Debug.Assert(File.Exists(absolutePath),String.Format("Path {0} doesn't exist",absolutePath));
366 return new FileInfo(absolutePath);
370 public void Delete(string relativePath)
372 var absolutePath = Path.Combine(RootPath, relativePath);
373 if (File.Exists(absolutePath))
375 File.Delete(absolutePath);
376 _ignoreFiles[absolutePath.ToLower()] = absolutePath.ToLower();
378 StatusKeeper.ClearFileStatus(absolutePath);