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))
213 /* if (Directory.Exists(filePath))
215 _agent.Post(new WorkflowState{AccountInfo=AccountInfo, Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
219 //Post a Change message for renames containing the old and new names
220 void OnRenameEvent(object sender, RenamedEventArgs e)
222 var oldFullPath = e.OldFullPath;
223 var fullPath = e.FullPath;
224 if (Ignore(oldFullPath) || Ignore(fullPath))
227 _agent.Post(new WorkflowState
229 AccountInfo=AccountInfo,
230 OldPath = oldFullPath,
231 OldFileName = e.OldName,
234 TriggeringChange = e.ChangeType
240 private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
242 {WatcherChangeTypes.Created,FileStatus.Created},
243 {WatcherChangeTypes.Changed,FileStatus.Modified},
244 {WatcherChangeTypes.Deleted,FileStatus.Deleted},
245 {WatcherChangeTypes.Renamed,FileStatus.Renamed}
248 private Dictionary<string,string> _ignoreFiles=new Dictionary<string, string>();
250 private WorkflowState UpdateFileStatus(WorkflowState state)
253 throw new ArgumentNullException("state");
254 if (String.IsNullOrWhiteSpace(state.Path))
255 throw new ArgumentException("The state's Path can't be empty","state");
256 Contract.EndContractBlock();
258 var path = state.Path;
259 var status = _statusDict[state.TriggeringChange];
260 var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
261 if (status == oldStatus)
263 state.Status = status;
267 if (state.Status == FileStatus.Renamed)
268 Workflow.ClearFileStatus(path);
270 state.Status = Workflow.SetFileStatus(path, status);
274 private WorkflowState UpdateOverlayStatus(WorkflowState state)
277 throw new ArgumentNullException("state");
278 Contract.EndContractBlock();
283 switch (state.Status)
285 case FileStatus.Created:
286 case FileStatus.Modified:
287 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
289 case FileStatus.Deleted:
290 //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
292 case FileStatus.Renamed:
293 this.StatusKeeper.ClearFileStatus(state.OldPath);
294 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
296 case FileStatus.Unchanged:
297 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
301 if (state.Status == FileStatus.Deleted)
302 NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
304 NativeMethods.RaiseChangeNotification(state.Path);
309 private WorkflowState UpdateFileChecksum(WorkflowState state)
314 if (state.Status == FileStatus.Deleted)
317 var path = state.Path;
318 //Skip calculation for folders
319 if (Directory.Exists(path))
322 var info = new FileInfo(path);
323 string hash = info.CalculateHash(StatusKeeper.BlockSize,StatusKeeper.BlockHash);
324 StatusKeeper.UpdateFileChecksum(path, hash);
330 //Does the file exist in the container's local folder?
331 public bool Exists(string relativePath)
333 if (String.IsNullOrWhiteSpace(relativePath))
334 throw new ArgumentNullException("relativePath");
335 //A RootPath must be set before calling this method
336 if (String.IsNullOrWhiteSpace(RootPath))
337 throw new InvalidOperationException("RootPath was not set");
338 Contract.EndContractBlock();
339 //Create the absolute path by combining the RootPath with the relativePath
340 var absolutePath=Path.Combine(RootPath, relativePath);
341 //Is this a valid file?
342 if (File.Exists(absolutePath))
345 if (Directory.Exists(absolutePath))
347 //Fail if it is neither
351 public FileSystemInfo GetFileSystemInfo(string relativePath)
353 if (String.IsNullOrWhiteSpace(relativePath))
354 throw new ArgumentNullException("relativePath");
355 //A RootPath must be set before calling this method
356 if (String.IsNullOrWhiteSpace(RootPath))
357 throw new InvalidOperationException("RootPath was not set");
358 Contract.EndContractBlock();
360 var absolutePath = Path.Combine(RootPath, relativePath);
362 if (Directory.Exists(absolutePath))
363 return new DirectoryInfo(absolutePath).WithProperCapitalization();
365 return new FileInfo(absolutePath).WithProperCapitalization();
369 public void Delete(string relativePath)
371 var absolutePath = Path.Combine(RootPath, relativePath).ToLower();
372 if (File.Exists(absolutePath))
376 File.Delete(absolutePath);
378 //The file may have been deleted by another thread. Just ignore the relevant exception
379 catch (FileNotFoundException) { }
381 else if (Directory.Exists(absolutePath))
385 Directory.Delete(absolutePath, true);
387 //The directory may have been deleted by another thread. Just ignore the relevant exception
388 catch (DirectoryNotFoundException){}
391 //_ignoreFiles[absolutePath] = absolutePath;
392 StatusKeeper.ClearFileStatus(absolutePath);