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 if (Ignore(state.Path))
79 return CompletedTask<object>.Default;
81 var networkState = NetworkGate.GetNetworkState(state.Path);
82 //Skip if the file is already being downloaded or uploaded and
83 //the change is create or modify
84 if (networkState != NetworkOperation.None &&
86 state.TriggeringChange == WatcherChangeTypes.Created ||
87 state.TriggeringChange == WatcherChangeTypes.Changed
89 return CompletedTask<object>.Default;
93 UpdateFileStatus(state);
94 UpdateOverlayStatus(state);
95 UpdateFileChecksum(state);
96 WorkflowAgent.Post(state);
98 catch (IOException exc)
100 if (File.Exists(state.Path))
102 Log.WarnFormat("File access error occured, retrying {0}\n{1}", state.Path, exc);
107 Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);
110 catch (Exception exc)
112 Log.WarnFormat("Error occured while indexing{0}. The file will be skipped\n{1}",
115 return CompletedTask<object>.Default;
120 get { return _watcher == null || !_watcher.EnableRaisingEvents; }
123 if (_watcher != null)
124 _watcher.EnableRaisingEvents = !value;
128 public string CachePath { get; set; }
130 private List<string> _selectivePaths = new List<string>();
131 public List<string> SelectivePaths
133 get { return _selectivePaths; }
134 set { _selectivePaths = value; }
138 public void Post(WorkflowState workflowState)
140 if (workflowState == null)
141 throw new ArgumentNullException("workflowState");
142 Contract.EndContractBlock();
144 _agent.Post(workflowState);
149 if (_watcher != null)
151 _watcher.Changed -= OnFileEvent;
152 _watcher.Created -= OnFileEvent;
153 _watcher.Deleted -= OnFileEvent;
154 _watcher.Renamed -= OnRenameEvent;
163 // Enumerate all files in the Pithos directory except those in the Fragment folder
164 // and files with a .ignore extension
165 public IEnumerable<string> EnumerateFiles(string searchPattern="*")
167 var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
168 where !Ignore(filePath)
170 return monitoredFiles;
173 public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
175 var rootDir = new DirectoryInfo(RootPath);
176 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
177 where !Ignore(file.FullName)
179 return monitoredFiles;
182 public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
184 var rootDir = new DirectoryInfo(RootPath);
185 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
186 where !Ignore(file.FullName)
187 select file.AsRelativeUrlTo(RootPath);
188 return monitoredFiles;
194 private bool Ignore(string filePath)
196 var pithosPath = Path.Combine(RootPath, "pithos");
197 if (pithosPath.Equals(filePath, StringComparison.InvariantCultureIgnoreCase))
199 if (filePath.StartsWith(CachePath))
201 if (_ignoreFiles.ContainsKey(filePath.ToLower()))
206 //Post a Change message for all events except rename
207 void OnFileEvent(object sender, FileSystemEventArgs e)
209 //Ignore events that affect the cache folder
210 var filePath = e.FullPath;
211 if (Ignore(filePath))
214 _agent.Post(new WorkflowState{AccountInfo=AccountInfo, Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
218 //Post a Change message for renames containing the old and new names
219 void OnRenameEvent(object sender, RenamedEventArgs e)
221 var oldFullPath = e.OldFullPath;
222 var fullPath = e.FullPath;
223 if (Ignore(oldFullPath) || Ignore(fullPath))
226 _agent.Post(new WorkflowState
228 AccountInfo=AccountInfo,
229 OldPath = oldFullPath,
230 OldFileName = e.OldName,
233 TriggeringChange = e.ChangeType
239 private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
241 {WatcherChangeTypes.Created,FileStatus.Created},
242 {WatcherChangeTypes.Changed,FileStatus.Modified},
243 {WatcherChangeTypes.Deleted,FileStatus.Deleted},
244 {WatcherChangeTypes.Renamed,FileStatus.Renamed}
247 private Dictionary<string,string> _ignoreFiles=new Dictionary<string, string>();
249 private WorkflowState UpdateFileStatus(WorkflowState state)
252 throw new ArgumentNullException("state");
253 if (String.IsNullOrWhiteSpace(state.Path))
254 throw new ArgumentException("The state's Path can't be empty","state");
255 Contract.EndContractBlock();
257 var path = state.Path;
258 var status = _statusDict[state.TriggeringChange];
259 var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
260 if (status == oldStatus)
262 state.Status = status;
266 if (state.Status == FileStatus.Renamed)
267 Workflow.ClearFileStatus(path);
269 state.Status = Workflow.SetFileStatus(path, status);
273 private WorkflowState UpdateOverlayStatus(WorkflowState state)
276 throw new ArgumentNullException("state");
277 Contract.EndContractBlock();
282 switch (state.Status)
284 case FileStatus.Created:
285 case FileStatus.Modified:
286 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
288 case FileStatus.Deleted:
289 //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
291 case FileStatus.Renamed:
292 this.StatusKeeper.ClearFileStatus(state.OldPath);
293 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
295 case FileStatus.Unchanged:
296 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
300 if (state.Status == FileStatus.Deleted)
301 NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
303 NativeMethods.RaiseChangeNotification(state.Path);
308 private WorkflowState UpdateFileChecksum(WorkflowState state)
313 if (state.Status == FileStatus.Deleted)
316 var path = state.Path;
317 //Skip calculation for folders
318 if (Directory.Exists(path))
321 var info = new FileInfo(path);
322 string hash = info.CalculateHash(StatusKeeper.BlockSize,StatusKeeper.BlockHash);
323 StatusKeeper.UpdateFileChecksum(path, hash);
329 //Does the file exist in the container's local folder?
330 public bool Exists(string relativePath)
332 if (String.IsNullOrWhiteSpace(relativePath))
333 throw new ArgumentNullException("relativePath");
334 //A RootPath must be set before calling this method
335 if (String.IsNullOrWhiteSpace(RootPath))
336 throw new InvalidOperationException("RootPath was not set");
337 Contract.EndContractBlock();
338 //Create the absolute path by combining the RootPath with the relativePath
339 var absolutePath=Path.Combine(RootPath, relativePath);
340 //Is this a valid file?
341 if (File.Exists(absolutePath))
344 if (Directory.Exists(absolutePath))
346 //Fail if it is neither
350 public FileSystemInfo GetFileSystemInfo(string relativePath)
352 if (String.IsNullOrWhiteSpace(relativePath))
353 throw new ArgumentNullException("relativePath");
354 //A RootPath must be set before calling this method
355 if (String.IsNullOrWhiteSpace(RootPath))
356 throw new InvalidOperationException("RootPath was not set");
357 Contract.EndContractBlock();
359 var absolutePath = Path.Combine(RootPath, relativePath);
361 if (Directory.Exists(absolutePath))
362 return new DirectoryInfo(absolutePath).WithProperCapitalization();
364 return new FileInfo(absolutePath).WithProperCapitalization();
368 public void Delete(string relativePath)
370 var absolutePath = Path.Combine(RootPath, relativePath).ToLower();
371 if (File.Exists(absolutePath))
375 File.Delete(absolutePath);
377 //The file may have been deleted by another thread. Just ignore the relevant exception
378 catch (FileNotFoundException) { }
380 else if (Directory.Exists(absolutePath))
384 Directory.Delete(absolutePath, true);
386 //The directory may have been deleted by another thread. Just ignore the relevant exception
387 catch (DirectoryNotFoundException){}
390 //_ignoreFiles[absolutePath] = absolutePath;
391 StatusKeeper.ClearFileStatus(absolutePath);