-#region
-/* -----------------------------------------------------------------------
- * <copyright file="FileAgent.cs" company="GRNet">
- *
- * Copyright 2011-2012 GRNET S.A. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- * copyright notice, this list of conditions and the following
- * disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- *
- * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and
- * documentation are those of the authors and should not be
- * interpreted as representing official policies, either expressed
- * or implied, of GRNET S.A.
- * </copyright>
- * -----------------------------------------------------------------------
- */
-#endregion
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Contracts;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Threading.Tasks;
-using Pithos.Interfaces;
-using Pithos.Network;
-using log4net;
-
-namespace Pithos.Core.Agents
-{
-// [Export]
- public class FileAgent
- {
- private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
-
- /*
- Agent<WorkflowState> _agent;
- */
- private FileSystemWatcher _watcher;
- private FileSystemWatcherAdapter _adapter;
- private FileEventIdleBatch _eventIdleBatch;
-
- //[Import]
- public IStatusKeeper StatusKeeper { get; set; }
-
- public IStatusNotification StatusNotification { get; set; }
- //[Import]
- public IPithosWorkflow Workflow { get; set; }
- //[Import]
- //public WorkflowAgent WorkflowAgent { get; set; }
-
- private AccountInfo AccountInfo { get; set; }
-
- internal string RootPath { get; set; }
-
- public TimeSpan IdleTimeout { get; set; }
-
- public PollAgent PollAgent { get; set; }
-
- private void ProcessBatchedEvents(FileSystemEventArgs[] fileEvents)
- {
- var paths = new HashSet<string>();
-
- foreach (var evt in fileEvents)
- {
- paths.Add(evt.FullPath);
- if (evt is MovedEventArgs)
- {
- paths.Add((evt as MovedEventArgs).OldFullPath);
- }
- else if (evt is RenamedEventArgs)
- {
- paths.Add((evt as RenamedEventArgs).OldFullPath);
- }
- }
-
- PollAgent.SynchNow(paths);
- }
-
-/*
- private void ProcessBatchedEvents(Dictionary<string, FileSystemEventArgs[]> fileEvents)
- {
- StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Uploading {0} files",fileEvents.Count));
- //Start with events that do not originate in one of the ignored folders
- var initialEvents = from evt in fileEvents
- where !IgnorePaths(evt.Key)
- select evt;
-
- IEnumerable<KeyValuePair<string, FileSystemEventArgs[]>> cleanEvents;
-
-
- var selectiveEnabled = Selectives.IsSelectiveEnabled(AccountInfo.AccountKey);
- //When selective sync is enabled,
- if (selectiveEnabled)
- {
- //Include all selected items
- var selectedEvents = from evt in initialEvents
- where Selectives.IsSelected(AccountInfo, evt.Key)
- select evt;
- //And all folder creations in the unselected folders
- var folderCreations = from evt in initialEvents
- let folderPath=evt.Key
- //The original folder may not exist due to renames. Just make sure that the path is not a file
- where !File.Exists(folderPath)
- //We only want unselected items
- && !Selectives.IsSelected(AccountInfo, folderPath)
- //Is there any creation event related to the folder?
- && evt.Value.Any(arg => arg.ChangeType == WatcherChangeTypes.Created)
- select evt;
- cleanEvents = selectedEvents.Union(folderCreations).ToList();
- }
- //If selective is disabled, only exclude the shared folders
- else
- {
- cleanEvents = (from evt in initialEvents
- where !evt.Key.IsSharedTo(AccountInfo)
- select evt).ToList();
- }
-
-
- foreach (var fileEvent in cleanEvents)
- {
- //var filePath = fileEvent.Key;
- var changes = fileEvent.Value;
-
- var isNotFile = !File.Exists(fileEvent.Key);
- foreach (var change in changes)
- {
- if (change.ChangeType == WatcherChangeTypes.Renamed)
- {
- var rename = (MovedEventArgs) change;
- _agent.Post(new WorkflowState(change)
- {
- AccountInfo = AccountInfo,
- OldPath = rename.OldFullPath,
- OldFileName = Path.GetFileName(rename.OldName),
- Path = rename.FullPath,
- FileName = Path.GetFileName(rename.Name),
- TriggeringChange = rename.ChangeType
- });
- }
- else
- {
- var isCreation = selectiveEnabled && isNotFile && change.ChangeType == WatcherChangeTypes.Created;
- _agent.Post(new WorkflowState(change)
- {
- AccountInfo = AccountInfo,
- Path = change.FullPath,
- FileName = Path.GetFileName(change.Name),
- TriggeringChange = change.ChangeType,
- IsCreation=isCreation
- });
- }
- }
- }
- StatusNotification.SetPithosStatus(PithosStatus.LocalComplete);
- }
-*/
-
- public void Start(AccountInfo accountInfo,string rootPath)
- {
- if (accountInfo==null)
- throw new ArgumentNullException("accountInfo");
- if (String.IsNullOrWhiteSpace(rootPath))
- throw new ArgumentNullException("rootPath");
- if (!Path.IsPathRooted(rootPath))
- throw new ArgumentException("rootPath must be an absolute path","rootPath");
- if (IdleTimeout == null)
- throw new InvalidOperationException("IdleTimeout must have a valid value");
- Contract.EndContractBlock();
-
- AccountInfo = accountInfo;
- RootPath = rootPath;
-
- _eventIdleBatch = new FileEventIdleBatch(PollAgent,(int)IdleTimeout.TotalMilliseconds, ProcessBatchedEvents);
-
- _watcher = new FileSystemWatcher(rootPath) { IncludeSubdirectories = true, InternalBufferSize = 8 * 4096 };
- _adapter = new FileSystemWatcherAdapter(_watcher,this);
-
- _adapter.Changed += OnFileEvent;
- _adapter.Created += OnFileEvent;
- _adapter.Deleted += OnFileEvent;
- //_adapter.Renamed += OnRenameEvent;
- _adapter.Moved += OnMoveEvent;
- _watcher.EnableRaisingEvents = true;
-
-/*
-
-
-
- _agent = Agent<WorkflowState>.Start(inbox =>
- {
- Action loop = null;
- loop = () =>
- {
- var message = inbox.Receive();
- var process=message.Then(Process,inbox.CancellationToken);
- inbox.LoopAsync(process,loop,ex=>
- Log.ErrorFormat("[ERROR] File Event Processing:\r{0}", ex));
- };
- loop();
- });*/
- }
-
-/*
- private Task<object> Process(WorkflowState state)
- {
- if (state==null)
- throw new ArgumentNullException("state");
- Contract.EndContractBlock();
-
- if (Ignore(state.Path))
- return CompletedTask<object>.Default;
-
- var networkState = NetworkGate.GetNetworkState(state.Path);
- //Skip if the file is already being downloaded or uploaded and
- //the change is create or modify
- if (networkState != NetworkOperation.None &&
- (
- state.TriggeringChange == WatcherChangeTypes.Created ||
- state.TriggeringChange == WatcherChangeTypes.Changed
- ))
- return CompletedTask<object>.Default;
-
- try
- {
- //StatusKeeper.EnsureFileState(state.Path);
-
- UpdateFileStatus(state);
- UpdateOverlayStatus(state);
- UpdateLastMD5(state);
- WorkflowAgent.Post(state);
- }
- catch (IOException exc)
- {
- if (File.Exists(state.Path))
- {
- Log.WarnFormat("File access error occured, retrying {0}\n{1}", state.Path, exc);
- _agent.Post(state);
- }
- else
- {
- Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);
- }
- }
- catch (Exception exc)
- {
- Log.WarnFormat("Error occured while indexing{0}. The file will be skipped\n{1}",
- state.Path, exc);
- }
- return CompletedTask<object>.Default;
- }
-
- public bool Pause
- {
- get { return _watcher == null || !_watcher.EnableRaisingEvents; }
- set
- {
- if (_watcher != null)
- _watcher.EnableRaisingEvents = !value;
- }
- }
-*/
-
- public string CachePath { get; set; }
-
- /*private List<string> _selectivePaths = new List<string>();
- public List<string> SelectivePaths
- {
- get { return _selectivePaths; }
- set { _selectivePaths = value; }
- }
-*/
- public Selectives Selectives { get; set; }
-
-
-/*
- public void Post(WorkflowState workflowState)
- {
- if (workflowState == null)
- throw new ArgumentNullException("workflowState");
- Contract.EndContractBlock();
-
- _agent.Post(workflowState);
- }
-
- public void Stop()
- {
- if (_watcher != null)
- {
- _watcher.Dispose();
- }
- _watcher = null;
-
- if (_agent!=null)
- _agent.Stop();
- }
-
-*/
- // Enumerate all files in the Pithos directory except those in the Fragment folder
- // and files with a .ignore extension
- public IEnumerable<string> EnumerateFiles(string searchPattern="*")
- {
- var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
- where !Ignore(filePath)
- orderby filePath ascending
- select filePath;
- return monitoredFiles;
- }
-
- public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
- {
- var rootDir = new DirectoryInfo(RootPath);
- var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
- where !Ignore(file.FullName)
- orderby file.FullName ascending
- select file;
- return monitoredFiles;
- }
-
- public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern="*")
- {
- var rootDir = new DirectoryInfo(RootPath);
- //Ensure folders appear first, to allow folder processing as soon as possilbe
- var folders = (from file in rootDir.EnumerateDirectories(searchPattern, SearchOption.AllDirectories)
- where !Ignore(file.FullName)
- orderby file.FullName ascending
- select file).ToList();
- var files = (from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
- where !Ignore(file.FullName)
- orderby file.Length ascending
- select file as FileSystemInfo).ToList();
- var monitoredFiles = folders
- //Process small files first, leaving expensive large files for last
- .Concat(files);
- return monitoredFiles;
- }
-
- public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
- {
- var rootDir = new DirectoryInfo(RootPath);
- var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
- where !Ignore(file.FullName)
- orderby file.FullName ascending
- select file.AsRelativeUrlTo(RootPath);
- return monitoredFiles;
- }
-
- public IEnumerable<string> EnumerateFilesSystemInfosAsRelativeUrls(string searchPattern="*")
- {
- var rootDir = new DirectoryInfo(RootPath);
- var monitoredFiles = from file in rootDir.EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories)
- where !Ignore(file.FullName)
- orderby file.FullName ascending
- select file.AsRelativeUrlTo(RootPath);
- return monitoredFiles;
- }
-
-
-
-
- public bool Ignore(string filePath)
- {
- if (IgnorePaths(filePath)) return true;
-
-
- //If selective sync is enabled,
- if (IsUnselectedRootFolder(filePath))
- return false;
- //Ignore if selective synchronization is defined,
- //And the target file is not below any of the selective paths
- var ignore = !Selectives.IsSelected(AccountInfo, filePath);
- return ignore;
- }
-
- public bool IsUnselectedRootFolder(string filePath)
- {
- return Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) //propagate folder events
- && Directory.Exists(filePath) //from the container root folder only. Note, in the first level below the account root path are the containers
- && FoundBelowRoot(filePath, RootPath, 2);
- }
-
- public bool IgnorePaths(string filePath)
- {
-//Ignore all first-level directories and files (ie at the container folders level)
- if (FoundBelowRoot(filePath, RootPath, 1))
- return true;
-
- //Ignore first-level items under the "others" folder (ie at the accounts folders level).
- var othersPath = Path.Combine(RootPath, FolderConstants.OthersFolder);
- if (FoundBelowRoot(filePath, othersPath, 1))
- return true;
-
- //Ignore second-level (container) folders under the "others" folder (ie at the container folders level).
- if (FoundBelowRoot(filePath, othersPath, 2))
- return true;
-
-
- //Ignore anything happening in the cache path
- if (filePath.StartsWith(CachePath))
- return true;
-
- //Finally, ignore events about one of the ignored files
- return _ignoreFiles.ContainsKey(filePath.ToLower());
- }
-
-/* private static bool FoundInRoot(string filePath, string rootPath)
- {
- //var rootDirectory = new DirectoryInfo(rootPath);
-
- //If the paths are equal, return true
- if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
- return true;
-
- //If the filepath is below the root path
- if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
- {
- //Get the relative path
- var relativePath = filePath.Substring(rootPath.Length + 1);
- //If the relativePath does NOT contains a path separator, we found a match
- return (!relativePath.Contains(@"\"));
- }
-
- //If the filepath is not under the root path, return false
- return false;
- }*/
-
-
- private static bool FoundBelowRoot(string filePath, string rootPath,int level)
- {
- //var rootDirectory = new DirectoryInfo(rootPath);
-
- //If the paths are equal, return true
- if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
- return true;
-
- //If the filepath is below the root path
- if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
- {
- //Get the relative path
- var relativePath = filePath.Substring(rootPath.Length + 1);
- //If the relativePath does NOT contains a path separator, we found a match
- var levels=relativePath.ToCharArray().Count(c=>c=='\\')+1;
- return levels==level;
- }
-
- //If the filepath is not under the root path, return false
- return false;
- }
-
- /*
- //Post a Change message for renames containing the old and new names
- void OnRenameEvent(object sender, RenamedEventArgs e)
- {
- var oldFullPath = e.OldFullPath;
- var fullPath = e.FullPath;
- if (Ignore(oldFullPath) || Ignore(fullPath))
- return;
-
- _agent.Post(new WorkflowState
- {
- AccountInfo=AccountInfo,
- OldPath = oldFullPath,
- OldFileName = e.OldName,
- Path = fullPath,
- FileName = e.Name,
- TriggeringChange = e.ChangeType
- });
- }
- */
-
- //Post a Change message for all events except rename
- void OnFileEvent(object sender, FileSystemEventArgs e)
- {
- //Ignore events that affect the cache folder
- var filePath = e.FullPath;
- if (Ignore(filePath))
- return;
- _eventIdleBatch.Post(e);
- }
-
- //Post a Change message for moves containing the old and new names
- void OnMoveEvent(object sender, MovedEventArgs e)
- {
- var oldFullPath = e.OldFullPath;
- var fullPath = e.FullPath;
-
-
- //If the source path is one of the ignored folders, ignore
- if (IgnorePaths(oldFullPath))
- return;
-
- //TODO: Must prevent move propagation if the source folder is blocked by selective sync
- //Ignore takes into account Selective Sync
- if (Ignore(fullPath))
- return;
- PollAgent.PostMove(e);
- _eventIdleBatch.Post(e);
- }
-
-
-
- private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
- {
- {WatcherChangeTypes.Created,FileStatus.Created},
- {WatcherChangeTypes.Changed,FileStatus.Modified},
- {WatcherChangeTypes.Deleted,FileStatus.Deleted},
- {WatcherChangeTypes.Renamed,FileStatus.Renamed}
- };
-
- private Dictionary<string, string> _ignoreFiles=new Dictionary<string, string>();
-
- private WorkflowState UpdateFileStatus(WorkflowState state)
- {
- if (state==null)
- throw new ArgumentNullException("state");
- if (String.IsNullOrWhiteSpace(state.Path))
- throw new ArgumentException("The state's Path can't be empty","state");
- Contract.EndContractBlock();
-
- var path = state.Path;
- var status = _statusDict[state.TriggeringChange];
- var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
- if (status == oldStatus)
- {
- state.Status = status;
- state.Skip = true;
- return state;
- }
- if (state.Status == FileStatus.Renamed)
- Workflow.ClearFileStatus(path);
-
- state.Status = Workflow.SetFileStatus(path, status);
- return state;
- }
-
- private WorkflowState UpdateOverlayStatus(WorkflowState state)
- {
- if (state==null)
- throw new ArgumentNullException("state");
- Contract.EndContractBlock();
-
- if (state.Skip)
- return state;
-
- switch (state.Status)
- {
- case FileStatus.Created:
- this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ETag).Wait();
- break;
- case FileStatus.Modified:
- this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();
- break;
- case FileStatus.Deleted:
- //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
- break;
- case FileStatus.Renamed:
- this.StatusKeeper.ClearFileStatus(state.OldPath);
- this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();
- break;
- case FileStatus.Unchanged:
- this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ETag).Wait();
- break;
- }
-
- if (state.Status == FileStatus.Deleted)
- NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
- else
- NativeMethods.RaiseChangeNotification(state.Path);
- return state;
- }
-
-
- private WorkflowState UpdateFileChecksum(WorkflowState state)
- {
- if (state.Skip)
- return state;
-
- if (state.Status == FileStatus.Deleted)
- return state;
-
- var path = state.Path;
- //Skip calculation for folders
- if (Directory.Exists(path))
- return state;
-
- var info = new FileInfo(path);
-
- using (StatusNotification.GetNotifier("Hashing {0}", "Finished Hashing {0}", info.Name))
- {
-
- var etag = info.ComputeShortHash(StatusNotification);
-
- var progress = new Progress<double>(d =>
- StatusNotification.Notify(new StatusNotification(String.Format("Hashing {0} of {1}", d, info.Name))));
-
- string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash,PollAgent.CancellationToken,progress);
- StatusKeeper.UpdateFileChecksum(path, etag, merkleHash);
-
- state.Hash = merkleHash;
- return state;
- }
- }
-
- //Does the file exist in the container's local folder?
- public bool Exists(string relativePath)
- {
- if (String.IsNullOrWhiteSpace(relativePath))
- throw new ArgumentNullException("relativePath");
- //A RootPath must be set before calling this method
- if (String.IsNullOrWhiteSpace(RootPath))
- throw new InvalidOperationException("RootPath was not set");
- Contract.EndContractBlock();
- //Create the absolute path by combining the RootPath with the relativePath
- var absolutePath=Path.Combine(RootPath, relativePath);
- //Is this a valid file?
- if (File.Exists(absolutePath))
- return true;
- //Or a directory?
- if (Directory.Exists(absolutePath))
- return true;
- //Fail if it is neither
- return false;
- }
-
- public static FileAgent GetFileAgent(AccountInfo accountInfo)
- {
- return GetFileAgent(accountInfo.AccountPath);
- }
-
- public static FileAgent GetFileAgent(string rootPath)
- {
- return AgentLocator<FileAgent>.Get(rootPath.ToLower());
- }
-
-
- public FileSystemInfo GetFileSystemInfo(string relativePath)
- {
- if (String.IsNullOrWhiteSpace(relativePath))
- throw new ArgumentNullException("relativePath");
- //A RootPath must be set before calling this method
- if (String.IsNullOrWhiteSpace(RootPath))
- throw new InvalidOperationException("RootPath was not set");
- Contract.EndContractBlock();
-
- var absolutePath = Path.Combine(RootPath, relativePath);
-
- if (Directory.Exists(absolutePath))
- return new DirectoryInfo(absolutePath).WithProperCapitalization();
- else
- return new FileInfo(absolutePath).WithProperCapitalization();
-
- }
-
- public void Delete(string relativePath)
- {
- var absolutePath = Path.Combine(RootPath, relativePath).ToLower();
- if (Log.IsDebugEnabled)
- Log.DebugFormat("Deleting {0}", absolutePath);
- if (File.Exists(absolutePath))
- {
- try
- {
- File.Delete(absolutePath);
- }
- //The file may have been deleted by another thread. Just ignore the relevant exception
- catch (FileNotFoundException) { }
- }
- else if (Directory.Exists(absolutePath))
- {
- DeleteWithRetry(absolutePath, 3);
- }
-
- //_ignoreFiles[absolutePath] = absolutePath;
- StatusKeeper.ClearFileStatus(absolutePath);
- }
-
- private static void DeleteWithRetry(string absolutePath, int retries)
- {
- try
- {
- var dirinfo = new DirectoryInfo(absolutePath);
- dirinfo.Attributes &= ~FileAttributes.ReadOnly;
- foreach (var info in dirinfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
- {
- info.Attributes &= ~FileAttributes.ReadOnly;
- }
- dirinfo.Refresh();
- dirinfo.Delete(true);
- }
- //The directory may have been deleted by another thread. Just ignore the relevant exception
- catch (DirectoryNotFoundException) { }
- catch (IOException)
- {
- if (retries>0)
- DeleteWithRetry(absolutePath,retries-1);
- else
- {
- throw;
- }
- }
- }
- }
-}
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="FileAgent.cs" company="GRNet">\r
+ * \r
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above\r
+ * copyright notice, this list of conditions and the following\r
+ * disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above\r
+ * copyright notice, this list of conditions and the following\r
+ * disclaimer in the documentation and/or other materials\r
+ * provided with the distribution.\r
+ *\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * The views and conclusions contained in the software and\r
+ * documentation are those of the authors and should not be\r
+ * interpreted as representing official policies, either expressed\r
+ * or implied, of GRNET S.A.\r
+ * </copyright>\r
+ * -----------------------------------------------------------------------\r
+ */\r
+#endregion\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Linq;\r
+using System.Reflection;\r
+using System.Threading.Tasks;\r
+using Pithos.Interfaces;\r
+using Pithos.Network;\r
+using log4net;\r
+\r
+namespace Pithos.Core.Agents\r
+{\r
+// [Export]\r
+ public class FileAgent\r
+ {\r
+ private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
+\r
+ /*\r
+ Agent<WorkflowState> _agent;\r
+ */\r
+ private FileSystemWatcher _watcher;\r
+ private FileSystemWatcherAdapter _adapter;\r
+ private FileEventIdleBatch _eventIdleBatch;\r
+\r
+ //[Import]\r
+ public IStatusKeeper StatusKeeper { get; set; }\r
+\r
+ public IStatusNotification StatusNotification { get; set; }\r
+ //[Import]\r
+ public IPithosWorkflow Workflow { get; set; }\r
+ //[Import]\r
+ //public WorkflowAgent WorkflowAgent { get; set; }\r
+\r
+ private AccountInfo AccountInfo { get; set; }\r
+\r
+ internal string RootPath { get; set; }\r
+ \r
+ public TimeSpan IdleTimeout { get; set; }\r
+\r
+ public PollAgent PollAgent { get; set; }\r
+\r
+ private void ProcessBatchedEvents(FileSystemEventArgs[] fileEvents)\r
+ {\r
+ var paths = new HashSet<string>();\r
+\r
+ foreach (var evt in fileEvents)\r
+ {\r
+ paths.Add(evt.FullPath);\r
+ if (evt is MovedEventArgs)\r
+ {\r
+ paths.Add((evt as MovedEventArgs).OldFullPath);\r
+ }\r
+ else if (evt is RenamedEventArgs)\r
+ {\r
+ paths.Add((evt as RenamedEventArgs).OldFullPath);\r
+ }\r
+ }\r
+ \r
+ PollAgent.SynchNow(paths);\r
+ }\r
+\r
+/*\r
+ private void ProcessBatchedEvents(Dictionary<string, FileSystemEventArgs[]> fileEvents)\r
+ {\r
+ StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Uploading {0} files",fileEvents.Count));\r
+ //Start with events that do not originate in one of the ignored folders\r
+ var initialEvents = from evt in fileEvents\r
+ where !IgnorePaths(evt.Key)\r
+ select evt;\r
+\r
+ IEnumerable<KeyValuePair<string, FileSystemEventArgs[]>> cleanEvents;\r
+\r
+ \r
+ var selectiveEnabled = Selectives.IsSelectiveEnabled(AccountInfo.AccountKey);\r
+ //When selective sync is enabled,\r
+ if (selectiveEnabled)\r
+ {\r
+ //Include all selected items\r
+ var selectedEvents = from evt in initialEvents\r
+ where Selectives.IsSelected(AccountInfo, evt.Key)\r
+ select evt; \r
+ //And all folder creations in the unselected folders\r
+ var folderCreations = from evt in initialEvents\r
+ let folderPath=evt.Key\r
+ //The original folder may not exist due to renames. Just make sure that the path is not a file\r
+ where !File.Exists(folderPath)\r
+ //We only want unselected items\r
+ && !Selectives.IsSelected(AccountInfo, folderPath)\r
+ //Is there any creation event related to the folder?\r
+ && evt.Value.Any(arg => arg.ChangeType == WatcherChangeTypes.Created)\r
+ select evt;\r
+ cleanEvents = selectedEvents.Union(folderCreations).ToList();\r
+ }\r
+ //If selective is disabled, only exclude the shared folders \r
+ else\r
+ {\r
+ cleanEvents = (from evt in initialEvents\r
+ where !evt.Key.IsSharedTo(AccountInfo)\r
+ select evt).ToList();\r
+ }\r
+\r
+\r
+ foreach (var fileEvent in cleanEvents)\r
+ {\r
+ //var filePath = fileEvent.Key;\r
+ var changes = fileEvent.Value;\r
+\r
+ var isNotFile = !File.Exists(fileEvent.Key);\r
+ foreach (var change in changes)\r
+ {\r
+ if (change.ChangeType == WatcherChangeTypes.Renamed)\r
+ {\r
+ var rename = (MovedEventArgs) change;\r
+ _agent.Post(new WorkflowState(change)\r
+ {\r
+ AccountInfo = AccountInfo,\r
+ OldPath = rename.OldFullPath,\r
+ OldFileName = Path.GetFileName(rename.OldName),\r
+ Path = rename.FullPath,\r
+ FileName = Path.GetFileName(rename.Name),\r
+ TriggeringChange = rename.ChangeType\r
+ });\r
+ }\r
+ else\r
+ {\r
+ var isCreation = selectiveEnabled && isNotFile && change.ChangeType == WatcherChangeTypes.Created;\r
+ _agent.Post(new WorkflowState(change)\r
+ {\r
+ AccountInfo = AccountInfo,\r
+ Path = change.FullPath,\r
+ FileName = Path.GetFileName(change.Name),\r
+ TriggeringChange = change.ChangeType,\r
+ IsCreation=isCreation\r
+ });\r
+ }\r
+ }\r
+ }\r
+ StatusNotification.SetPithosStatus(PithosStatus.LocalComplete);\r
+ }\r
+*/\r
+\r
+ public void Start(AccountInfo accountInfo,string rootPath)\r
+ {\r
+ if (accountInfo==null)\r
+ throw new ArgumentNullException("accountInfo");\r
+ if (String.IsNullOrWhiteSpace(rootPath))\r
+ throw new ArgumentNullException("rootPath");\r
+ if (!Path.IsPathRooted(rootPath))\r
+ throw new ArgumentException("rootPath must be an absolute path","rootPath");\r
+ if (IdleTimeout == null)\r
+ throw new InvalidOperationException("IdleTimeout must have a valid value");\r
+ Contract.EndContractBlock();\r
+\r
+ AccountInfo = accountInfo;\r
+ RootPath = rootPath;\r
+ \r
+ _eventIdleBatch = new FileEventIdleBatch(PollAgent,(int)IdleTimeout.TotalMilliseconds, ProcessBatchedEvents);\r
+\r
+ _watcher = new FileSystemWatcher(rootPath) { IncludeSubdirectories = true, InternalBufferSize = 8 * 4096 };\r
+ _adapter = new FileSystemWatcherAdapter(_watcher,this);\r
+\r
+ _adapter.Changed += OnFileEvent;\r
+ _adapter.Created += OnFileEvent;\r
+ _adapter.Deleted += OnFileEvent;\r
+ //_adapter.Renamed += OnRenameEvent;\r
+ _adapter.Moved += OnMoveEvent;\r
+ _watcher.EnableRaisingEvents = true;\r
+\r
+/* \r
+\r
+\r
+\r
+ _agent = Agent<WorkflowState>.Start(inbox =>\r
+ {\r
+ Action loop = null;\r
+ loop = () =>\r
+ {\r
+ var message = inbox.Receive();\r
+ var process=message.Then(Process,inbox.CancellationToken); \r
+ inbox.LoopAsync(process,loop,ex=>\r
+ Log.ErrorFormat("[ERROR] File Event Processing:\r{0}", ex));\r
+ };\r
+ loop();\r
+ });*/\r
+ }\r
+\r
+/*\r
+ private Task<object> Process(WorkflowState state)\r
+ {\r
+ if (state==null)\r
+ throw new ArgumentNullException("state");\r
+ Contract.EndContractBlock();\r
+\r
+ if (Ignore(state.Path))\r
+ return CompletedTask<object>.Default;\r
+\r
+ var networkState = NetworkGate.GetNetworkState(state.Path);\r
+ //Skip if the file is already being downloaded or uploaded and \r
+ //the change is create or modify\r
+ if (networkState != NetworkOperation.None &&\r
+ (\r
+ state.TriggeringChange == WatcherChangeTypes.Created ||\r
+ state.TriggeringChange == WatcherChangeTypes.Changed\r
+ ))\r
+ return CompletedTask<object>.Default;\r
+\r
+ try\r
+ {\r
+ //StatusKeeper.EnsureFileState(state.Path);\r
+ \r
+ UpdateFileStatus(state);\r
+ UpdateOverlayStatus(state);\r
+ UpdateLastMD5(state);\r
+ WorkflowAgent.Post(state);\r
+ }\r
+ catch (IOException exc)\r
+ {\r
+ if (File.Exists(state.Path))\r
+ {\r
+ Log.WarnFormat("File access error occured, retrying {0}\n{1}", state.Path, exc);\r
+ _agent.Post(state);\r
+ }\r
+ else\r
+ {\r
+ Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);\r
+ }\r
+ }\r
+ catch (Exception exc)\r
+ {\r
+ Log.WarnFormat("Error occured while indexing{0}. The file will be skipped\n{1}",\r
+ state.Path, exc);\r
+ }\r
+ return CompletedTask<object>.Default;\r
+ }\r
+\r
+ public bool Pause\r
+ {\r
+ get { return _watcher == null || !_watcher.EnableRaisingEvents; }\r
+ set\r
+ {\r
+ if (_watcher != null)\r
+ _watcher.EnableRaisingEvents = !value; \r
+ }\r
+ }\r
+*/\r
+\r
+ public string CachePath { get; set; }\r
+\r
+ /*private List<string> _selectivePaths = new List<string>();\r
+ public List<string> SelectivePaths\r
+ {\r
+ get { return _selectivePaths; }\r
+ set { _selectivePaths = value; }\r
+ }\r
+*/\r
+ public Selectives Selectives { get; set; }\r
+\r
+\r
+/*\r
+ public void Post(WorkflowState workflowState)\r
+ {\r
+ if (workflowState == null)\r
+ throw new ArgumentNullException("workflowState");\r
+ Contract.EndContractBlock();\r
+\r
+ _agent.Post(workflowState);\r
+ }\r
+\r
+ public void Stop()\r
+ {\r
+ if (_watcher != null)\r
+ {\r
+ _watcher.Dispose();\r
+ }\r
+ _watcher = null;\r
+\r
+ if (_agent!=null)\r
+ _agent.Stop();\r
+ }\r
+\r
+*/\r
+ // Enumerate all files in the Pithos directory except those in the Fragment folder\r
+ // and files with a .ignore extension\r
+ public IEnumerable<string> EnumerateFiles(string searchPattern="*")\r
+ {\r
+ var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)\r
+ where !Ignore(filePath)\r
+ orderby filePath ascending \r
+ select filePath;\r
+ return monitoredFiles;\r
+ }\r
+\r
+ public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")\r
+ {\r
+ var rootDir = new DirectoryInfo(RootPath);\r
+ var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)\r
+ where !Ignore(file.FullName)\r
+ orderby file.FullName ascending \r
+ select file;\r
+ return monitoredFiles;\r
+ } \r
+\r
+ public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern="*")\r
+ {\r
+ var rootDir = new DirectoryInfo(RootPath);\r
+ //Ensure folders appear first, to allow folder processing as soon as possilbe\r
+ var folders = (from file in rootDir.EnumerateDirectories(searchPattern, SearchOption.AllDirectories)\r
+ where !Ignore(file.FullName)\r
+ orderby file.FullName ascending\r
+ select file).ToList();\r
+ var files = (from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)\r
+ where !Ignore(file.FullName)\r
+ orderby file.Length ascending\r
+ select file as FileSystemInfo).ToList();\r
+ var monitoredFiles = folders\r
+ //Process small files first, leaving expensive large files for last\r
+ .Concat(files);\r
+ return monitoredFiles;\r
+ } \r
+\r
+ public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")\r
+ {\r
+ var rootDir = new DirectoryInfo(RootPath);\r
+ var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)\r
+ where !Ignore(file.FullName)\r
+ orderby file.FullName ascending \r
+ select file.AsRelativeUrlTo(RootPath);\r
+ return monitoredFiles;\r
+ } \r
+\r
+ public IEnumerable<string> EnumerateFilesSystemInfosAsRelativeUrls(string searchPattern="*")\r
+ {\r
+ var rootDir = new DirectoryInfo(RootPath);\r
+ var monitoredFiles = from file in rootDir.EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories)\r
+ where !Ignore(file.FullName)\r
+ orderby file.FullName ascending \r
+ select file.AsRelativeUrlTo(RootPath);\r
+ return monitoredFiles;\r
+ } \r
+\r
+\r
+ \r
+\r
+ public bool Ignore(string filePath)\r
+ {\r
+ if (IgnorePaths(filePath)) return true;\r
+\r
+\r
+ //If selective sync is enabled, \r
+ if (IsUnselectedRootFolder(filePath))\r
+ return false;\r
+ //Ignore if selective synchronization is defined, \r
+ //And the target file is not below any of the selective paths\r
+ var ignore = !Selectives.IsSelected(AccountInfo, filePath);\r
+ return ignore;\r
+ }\r
+\r
+ public bool IsUnselectedRootFolder(string filePath)\r
+ {\r
+ return Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) //propagate folder events \r
+ && Directory.Exists(filePath) //from the container root folder only. Note, in the first level below the account root path are the containers\r
+ && FoundBelowRoot(filePath, RootPath, 2);\r
+ }\r
+\r
+ public bool IgnorePaths(string filePath)\r
+ {\r
+//Ignore all first-level directories and files (ie at the container folders level)\r
+ if (FoundBelowRoot(filePath, RootPath, 1))\r
+ return true;\r
+\r
+ //Ignore first-level items under the "others" folder (ie at the accounts folders level).\r
+ var othersPath = Path.Combine(RootPath, FolderConstants.OthersFolder);\r
+ if (FoundBelowRoot(filePath, othersPath, 1))\r
+ return true;\r
+\r
+ //Ignore second-level (container) folders under the "others" folder (ie at the container folders level). \r
+ if (FoundBelowRoot(filePath, othersPath, 2))\r
+ return true;\r
+\r
+\r
+ //Ignore anything happening in the cache path\r
+ if (filePath.StartsWith(CachePath))\r
+ return true;\r
+ \r
+ //Finally, ignore events about one of the ignored files\r
+ return _ignoreFiles.ContainsKey(filePath.ToLower());\r
+ }\r
+\r
+/* private static bool FoundInRoot(string filePath, string rootPath)\r
+ {\r
+ //var rootDirectory = new DirectoryInfo(rootPath);\r
+\r
+ //If the paths are equal, return true\r
+ if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))\r
+ return true;\r
+\r
+ //If the filepath is below the root path\r
+ if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))\r
+ {\r
+ //Get the relative path\r
+ var relativePath = filePath.Substring(rootPath.Length + 1);\r
+ //If the relativePath does NOT contains a path separator, we found a match\r
+ return (!relativePath.Contains(@"\"));\r
+ }\r
+\r
+ //If the filepath is not under the root path, return false\r
+ return false; \r
+ }*/\r
+\r
+\r
+ private static bool FoundBelowRoot(string filePath, string rootPath,int level)\r
+ {\r
+ //var rootDirectory = new DirectoryInfo(rootPath);\r
+\r
+ //If the paths are equal, return true\r
+ if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))\r
+ return true;\r
+\r
+ //If the filepath is below the root path\r
+ if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))\r
+ {\r
+ //Get the relative path\r
+ var relativePath = filePath.Substring(rootPath.Length + 1);\r
+ //If the relativePath does NOT contains a path separator, we found a match\r
+ var levels=relativePath.ToCharArray().Count(c=>c=='\\')+1; \r
+ return levels==level;\r
+ }\r
+\r
+ //If the filepath is not under the root path, return false\r
+ return false; \r
+ }\r
+\r
+ /*\r
+ //Post a Change message for renames containing the old and new names\r
+ void OnRenameEvent(object sender, RenamedEventArgs e)\r
+ {\r
+ var oldFullPath = e.OldFullPath;\r
+ var fullPath = e.FullPath;\r
+ if (Ignore(oldFullPath) || Ignore(fullPath))\r
+ return;\r
+\r
+ _agent.Post(new WorkflowState\r
+ {\r
+ AccountInfo=AccountInfo,\r
+ OldPath = oldFullPath,\r
+ OldFileName = e.OldName,\r
+ Path = fullPath,\r
+ FileName = e.Name,\r
+ TriggeringChange = e.ChangeType\r
+ });\r
+ }\r
+ */\r
+\r
+ //Post a Change message for all events except rename\r
+ void OnFileEvent(object sender, FileSystemEventArgs e)\r
+ {\r
+ //Ignore events that affect the cache folder\r
+ var filePath = e.FullPath;\r
+ if (Ignore(filePath))\r
+ return;\r
+ _eventIdleBatch.Post(e);\r
+ }\r
+\r
+ //Post a Change message for moves containing the old and new names\r
+ void OnMoveEvent(object sender, MovedEventArgs e)\r
+ {\r
+ var oldFullPath = e.OldFullPath;\r
+ var fullPath = e.FullPath;\r
+ \r
+\r
+ //If the source path is one of the ignored folders, ignore\r
+ if (IgnorePaths(oldFullPath)) \r
+ return;\r
+\r
+ //TODO: Must prevent move propagation if the source folder is blocked by selective sync\r
+ //Ignore takes into account Selective Sync\r
+ if (Ignore(fullPath))\r
+ return;\r
+ PollAgent.PostMove(e);\r
+ _eventIdleBatch.Post(e);\r
+ }\r
+\r
+\r
+\r
+ private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>\r
+ {\r
+ {WatcherChangeTypes.Created,FileStatus.Created},\r
+ {WatcherChangeTypes.Changed,FileStatus.Modified},\r
+ {WatcherChangeTypes.Deleted,FileStatus.Deleted},\r
+ {WatcherChangeTypes.Renamed,FileStatus.Renamed}\r
+ };\r
+\r
+ private Dictionary<string, string> _ignoreFiles=new Dictionary<string, string>();\r
+\r
+ private WorkflowState UpdateFileStatus(WorkflowState state)\r
+ {\r
+ if (state==null)\r
+ throw new ArgumentNullException("state");\r
+ if (String.IsNullOrWhiteSpace(state.Path))\r
+ throw new ArgumentException("The state's Path can't be empty","state");\r
+ Contract.EndContractBlock();\r
+\r
+ var path = state.Path;\r
+ var status = _statusDict[state.TriggeringChange];\r
+ var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);\r
+ if (status == oldStatus)\r
+ {\r
+ state.Status = status;\r
+ state.Skip = true;\r
+ return state;\r
+ }\r
+ if (state.Status == FileStatus.Renamed)\r
+ Workflow.ClearFileStatus(path);\r
+\r
+ state.Status = Workflow.SetFileStatus(path, status);\r
+ return state;\r
+ }\r
+\r
+ /* private WorkflowState UpdateOverlayStatus(WorkflowState state)\r
+ {\r
+ if (state==null)\r
+ throw new ArgumentNullException("state");\r
+ Contract.EndContractBlock();\r
+\r
+ if (state.Skip)\r
+ return state;\r
+\r
+ switch (state.Status)\r
+ {\r
+ case FileStatus.Created:\r
+ this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ETag).Wait();\r
+ break;\r
+ case FileStatus.Modified:\r
+ this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();\r
+ break;\r
+ case FileStatus.Deleted:\r
+ //this.StatusAgent.RemoveFileOverlayStatus(state.Path);\r
+ break;\r
+ case FileStatus.Renamed:\r
+ this.StatusKeeper.ClearFileStatus(state.OldPath);\r
+ this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();\r
+ break;\r
+ case FileStatus.Unchanged:\r
+ this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ETag).Wait();\r
+ break;\r
+ }\r
+\r
+ if (state.Status == FileStatus.Deleted)\r
+ NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));\r
+ else\r
+ NativeMethods.RaiseChangeNotification(state.Path);\r
+ return state;\r
+ }\r
+\r
+\r
+ private WorkflowState UpdateFileChecksum(WorkflowState state)\r
+ {\r
+ if (state.Skip)\r
+ return state;\r
+\r
+ if (state.Status == FileStatus.Deleted)\r
+ return state;\r
+\r
+ var path = state.Path;\r
+ //Skip calculation for folders\r
+ if (Directory.Exists(path))\r
+ return state;\r
+\r
+ var info = new FileInfo(path);\r
+\r
+ using (StatusNotification.GetNotifier("Hashing {0}", "Finished Hashing {0}", info.Name))\r
+ {\r
+\r
+ var etag = info.ComputeShortHash(StatusNotification);\r
+\r
+ var progress = new Progress<double>(d =>\r
+ StatusNotification.Notify(new StatusNotification(String.Format("Hashing {0} of {1}", d, info.Name))));\r
+\r
+ string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash,PollAgent.CancellationToken,progress);\r
+ StatusKeeper.UpdateFileChecksum(path, etag, merkleHash);\r
+\r
+ state.Hash = merkleHash;\r
+ return state;\r
+ }\r
+ }*/\r
+\r
+ //Does the file exist in the container's local folder?\r
+ public bool Exists(string relativePath)\r
+ {\r
+ if (String.IsNullOrWhiteSpace(relativePath))\r
+ throw new ArgumentNullException("relativePath");\r
+ //A RootPath must be set before calling this method\r
+ if (String.IsNullOrWhiteSpace(RootPath))\r
+ throw new InvalidOperationException("RootPath was not set");\r
+ Contract.EndContractBlock();\r
+ //Create the absolute path by combining the RootPath with the relativePath\r
+ var absolutePath=Path.Combine(RootPath, relativePath);\r
+ //Is this a valid file?\r
+ if (File.Exists(absolutePath))\r
+ return true;\r
+ //Or a directory?\r
+ if (Directory.Exists(absolutePath))\r
+ return true;\r
+ //Fail if it is neither\r
+ return false;\r
+ }\r
+\r
+ public static FileAgent GetFileAgent(AccountInfo accountInfo)\r
+ {\r
+ return GetFileAgent(accountInfo.AccountPath);\r
+ }\r
+\r
+ public static FileAgent GetFileAgent(string rootPath)\r
+ {\r
+ return AgentLocator<FileAgent>.Get(rootPath.ToLower());\r
+ }\r
+\r
+\r
+ public FileSystemInfo GetFileSystemInfo(string relativePath)\r
+ {\r
+ if (String.IsNullOrWhiteSpace(relativePath))\r
+ throw new ArgumentNullException("relativePath");\r
+ //A RootPath must be set before calling this method\r
+ if (String.IsNullOrWhiteSpace(RootPath))\r
+ throw new InvalidOperationException("RootPath was not set"); \r
+ Contract.EndContractBlock(); \r
+\r
+ var absolutePath = Path.Combine(RootPath, relativePath);\r
+\r
+ if (Directory.Exists(absolutePath))\r
+ return new DirectoryInfo(absolutePath).WithProperCapitalization();\r
+ else\r
+ return new FileInfo(absolutePath).WithProperCapitalization();\r
+ \r
+ }\r
+\r
+ public void Delete(string relativePath)\r
+ {\r
+ var absolutePath = Path.Combine(RootPath, relativePath).ToLower();\r
+ if (Log.IsDebugEnabled)\r
+ Log.DebugFormat("Deleting {0}", absolutePath);\r
+ if (File.Exists(absolutePath))\r
+ { \r
+ try\r
+ {\r
+ File.Delete(absolutePath);\r
+ }\r
+ //The file may have been deleted by another thread. Just ignore the relevant exception\r
+ catch (FileNotFoundException) { }\r
+ }\r
+ else if (Directory.Exists(absolutePath))\r
+ {\r
+ DeleteWithRetry(absolutePath, 3);\r
+ }\r
+\r
+ //_ignoreFiles[absolutePath] = absolutePath; \r
+ StatusKeeper.ClearFileStatus(absolutePath);\r
+ }\r
+\r
+ private static void DeleteWithRetry(string absolutePath, int retries)\r
+ {\r
+ try\r
+ {\r
+ var dirinfo = new DirectoryInfo(absolutePath);\r
+ dirinfo.Attributes &= ~FileAttributes.ReadOnly;\r
+ foreach (var info in dirinfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))\r
+ {\r
+ info.Attributes &= ~FileAttributes.ReadOnly;\r
+ }\r
+ dirinfo.Refresh();\r
+ dirinfo.Delete(true);\r
+ }\r
+ //The directory may have been deleted by another thread. Just ignore the relevant exception\r
+ catch (DirectoryNotFoundException) { }\r
+ catch (IOException)\r
+ {\r
+ if (retries>0)\r
+ DeleteWithRetry(absolutePath,retries-1);\r
+ else\r
+ {\r
+ throw;\r
+ }\r
+ }\r
+ }\r
+ }\r
+}\r