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 System.Threading.Tasks.Dataflow;
11 using Pithos.Interfaces;
16 namespace Pithos.Core.Agents
19 public class FileAgent
21 //Agent<WorkflowState> _agent;
22 TransformBlock<WorkflowState, WorkflowState> _agent;
23 private FileSystemWatcher _watcher;
26 public IStatusKeeper StatusKeeper { get; set; }
28 public IPithosWorkflow Workflow { get; set; }
30 public WorkflowAgent WorkflowAgent { get; set; }
32 private AccountInfo AccountInfo { get; set; }
34 private string RootPath { get; set; }
36 private static readonly ILog Log = LogManager.GetLogger("FileAgent");
40 _agent = new TransformBlock<WorkflowState, WorkflowState>(async message =>
41 await TaskEx.Run(()=>Process(message)));
44 private WorkflowState Process(WorkflowState message)
46 Debug.Assert(!Ignore(message.Path));
50 //Skip if the file is already being downloaded or uploaded and
51 //the change is create or modify
52 var networkState = NetworkGate.GetNetworkState(message.Path);
53 if (networkState != NetworkOperation.None &&
54 (message.TriggeringChange == WatcherChangeTypes.Created ||
55 message.TriggeringChange == WatcherChangeTypes.Changed))
58 UpdateFileStatus(message);
59 UpdateOverlayStatus(message);
60 UpdateFileChecksum(message);
63 catch (IOException exc)
65 if (File.Exists(message.Path))
67 Log.WarnFormat("File access error occured, retrying {0}\n{1}", message.Path, exc);
72 Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", message.Path, exc);
77 Log.WarnFormat("Error occured while indexing{0}. The file will be skipped}\n{1}",
84 public void Start(AccountInfo accountInfo,string rootPath)
86 if (accountInfo==null)
87 throw new ArgumentNullException("accountInfo");
88 if (String.IsNullOrWhiteSpace(rootPath))
89 throw new ArgumentNullException("rootPath");
90 if (!Path.IsPathRooted(rootPath))
91 throw new ArgumentException("rootPath must be an absolute path","rootPath");
92 Contract.EndContractBlock();
94 _agent.LinkTo(WorkflowAgent.Agent);
96 AccountInfo = accountInfo;
98 _watcher = new FileSystemWatcher(rootPath);
99 _watcher.IncludeSubdirectories = true;
100 _watcher.Changed += OnFileEvent;
101 _watcher.Created += OnFileEvent;
102 _watcher.Deleted += OnFileEvent;
103 _watcher.Renamed += OnRenameEvent;
104 _watcher.EnableRaisingEvents = true;
110 get { return _watcher == null || !_watcher.EnableRaisingEvents; }
113 if (_watcher != null)
114 _watcher.EnableRaisingEvents = !value;
118 public string CachePath { get; set; }
120 private List<string> _selectivePaths = new List<string>();
121 public List<string> SelectivePaths
123 get { return _selectivePaths; }
124 set { _selectivePaths = value; }
128 public void Post(WorkflowState workflowState)
130 if (workflowState == null)
131 throw new ArgumentNullException("workflowState");
132 Contract.EndContractBlock();
134 _agent.Post(workflowState);
139 if (_watcher != null)
141 _watcher.Changed -= OnFileEvent;
142 _watcher.Created -= OnFileEvent;
143 _watcher.Deleted -= OnFileEvent;
144 _watcher.Renamed -= OnRenameEvent;
153 // Enumerate all files in the Pithos directory except those in the Fragment folder
154 // and files with a .ignore extension
155 public IEnumerable<string> EnumerateFiles(string searchPattern="*")
157 var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
158 where !Ignore(filePath)
160 return monitoredFiles;
163 public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
165 var rootDir = new DirectoryInfo(RootPath);
166 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
167 where !Ignore(file.FullName)
169 return monitoredFiles;
172 public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
174 var rootDir = new DirectoryInfo(RootPath);
175 var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
176 where !Ignore(file.FullName)
177 select file.AsRelativeUrlTo(RootPath);
178 return monitoredFiles;
184 private bool Ignore(string filePath)
186 if (filePath.StartsWith(CachePath))
188 if (_ignoreFiles.ContainsKey(filePath.ToLower()))
193 //Post a Change message for all events except rename
194 void OnFileEvent(object sender, FileSystemEventArgs e)
196 //Ignore events that affect the cache folder
197 var filePath = e.FullPath;
198 if (Ignore(filePath))
201 _agent.Post(new WorkflowState{AccountInfo=AccountInfo, Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
205 //Post a Change message for renames containing the old and new names
206 void OnRenameEvent(object sender, RenamedEventArgs e)
208 var oldFullPath = e.OldFullPath;
209 var fullPath = e.FullPath;
210 if (Ignore(oldFullPath) || Ignore(fullPath))
213 _agent.Post(new WorkflowState
215 AccountInfo=AccountInfo,
216 OldPath = oldFullPath,
217 OldFileName = e.OldName,
220 TriggeringChange = e.ChangeType
226 private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
228 {WatcherChangeTypes.Created,FileStatus.Created},
229 {WatcherChangeTypes.Changed,FileStatus.Modified},
230 {WatcherChangeTypes.Deleted,FileStatus.Deleted},
231 {WatcherChangeTypes.Renamed,FileStatus.Renamed}
234 private Dictionary<string,string> _ignoreFiles=new Dictionary<string, string>();
236 private WorkflowState UpdateFileStatus(WorkflowState state)
239 throw new ArgumentNullException("state");
240 if (String.IsNullOrWhiteSpace(state.Path))
241 throw new ArgumentException("The state's Path can't be empty","state");
242 Contract.EndContractBlock();
244 var path = state.Path;
245 var status = _statusDict[state.TriggeringChange];
246 var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
247 if (status == oldStatus)
249 state.Status = status;
253 if (state.Status == FileStatus.Renamed)
254 Workflow.ClearFileStatus(path);
256 state.Status = Workflow.SetFileStatus(path, status);
260 private WorkflowState UpdateOverlayStatus(WorkflowState state)
263 throw new ArgumentNullException("state");
264 Contract.EndContractBlock();
269 switch (state.Status)
271 case FileStatus.Created:
272 case FileStatus.Modified:
273 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
275 case FileStatus.Deleted:
276 //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
278 case FileStatus.Renamed:
279 this.StatusKeeper.ClearFileStatus(state.OldPath);
280 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
282 case FileStatus.Unchanged:
283 this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
287 if (state.Status == FileStatus.Deleted)
288 NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
290 NativeMethods.RaiseChangeNotification(state.Path);
295 private WorkflowState UpdateFileChecksum(WorkflowState state)
300 if (state.Status == FileStatus.Deleted)
303 var path = state.Path;
304 //Skip calculation for folders
305 if (Directory.Exists(path))
308 var info = new FileInfo(path);
309 string hash = info.CalculateHash(StatusKeeper.BlockSize,StatusKeeper.BlockHash);
310 StatusKeeper.UpdateFileChecksum(path, hash);
316 //Does the file exist in the container's local folder?
317 public bool Exists(string relativePath)
319 if (String.IsNullOrWhiteSpace(relativePath))
320 throw new ArgumentNullException("relativePath");
321 //A RootPath must be set before calling this method
322 if (String.IsNullOrWhiteSpace(RootPath))
323 throw new InvalidOperationException("RootPath was not set");
324 Contract.EndContractBlock();
325 //Create the absolute path by combining the RootPath with the relativePath
326 var absolutePath=Path.Combine(RootPath, relativePath);
327 //Is this a valid file?
328 if (File.Exists(absolutePath))
331 if (Directory.Exists(absolutePath))
333 //Fail if it is neither
337 public FileInfo GetFileInfo(string relativePath)
339 if (String.IsNullOrWhiteSpace(relativePath))
340 throw new ArgumentNullException("relativePath");
341 //A RootPath must be set before calling this method
342 if (String.IsNullOrWhiteSpace(RootPath))
343 throw new InvalidOperationException("RootPath was not set");
344 Contract.EndContractBlock();
346 var absolutePath = Path.Combine(RootPath, relativePath);
347 // Debug.Assert(File.Exists(absolutePath),String.Format("Path {0} doesn't exist",absolutePath));
349 return new FileInfo(absolutePath);
353 public void Delete(string relativePath)
355 var absolutePath = Path.Combine(RootPath, relativePath);
356 if (File.Exists(absolutePath))
358 File.Delete(absolutePath);
359 _ignoreFiles[absolutePath.ToLower()] = absolutePath.ToLower();
361 StatusKeeper.ClearFileStatus(absolutePath);