#region /* ----------------------------------------------------------------------- * * * 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. * * ----------------------------------------------------------------------- */ #endregion using System.Collections.Concurrent; using System.ComponentModel.Composition; using System.Diagnostics.Contracts; using System.IO; using System.Net; using System.Reflection; using System.Threading.Tasks.Dataflow; using Pithos.Interfaces; using Pithos.Network; using System; using log4net; namespace Pithos.Core.Agents { /// /// The Delete Agent is used to delete files from the Pithos server with high priority, /// blocking the network agent through the ProceedEvent until all pending deletions complete /// [Export] public class DeleteAgent { private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); [Import] public IStatusKeeper StatusKeeper { get; set; } public IStatusNotification StatusNotification { get; set; } //A separate agent is used to execute delete actions immediatelly; private readonly ActionBlock _deleteAgent; //The Proceed event signals the the network agent to proceed with processing. //Essentially this event pauses the network agent to give priority to the deletion agent //Initially the event is signalled because we don't need to pause private readonly AsyncManualResetEvent _proceedEvent = new AsyncManualResetEvent(true); public AsyncManualResetEvent ProceedEvent { get { return _proceedEvent; } } //Deleted file names are stored in memory so we can check that a file has already been deleted. //and avoid sending duplicate delete commands readonly ConcurrentDictionary _deletedFiles = new ConcurrentDictionary(); public DeleteAgent() { _deleteAgent = new ActionBlock(message => ProcessDelete(message), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 }); } /// /// Processes cloud delete actions /// /// The delete action to execute /// /// /// When a file/folder is deleted locally, we must delete it ASAP from the server and block any download /// operations that may be in progress. /// /// A separate agent is used to process deletes because the main agent may be busy with a long operation. /// /// private void ProcessDelete(CloudDeleteAction action) { if (action == null) throw new ArgumentNullException("action"); if (action.AccountInfo == null) throw new ArgumentException("The action.AccountInfo is empty", "action"); Contract.EndContractBlock(); var accountInfo = action.AccountInfo; using (log4net.ThreadContext.Stacks["Operation"].Push("ProcessDelete")) { Log.InfoFormat("[ACTION] Start Processing {0}", action); var cloudFile = action.CloudFile; try { //Acquire a lock on the deleted file to prevent uploading/downloading operations from the normal //agent using (NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting)) { //Add the file URL to the deleted files list var key = GetFileKey(action.CloudFile); _deletedFiles[key] = DateTime.Now; _proceedEvent.Reset(); // and then delete the file from the server DeleteCloudFile(accountInfo, cloudFile); Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile, action.CloudFile.Name); } } catch (WebException exc) { Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc); } catch (OperationCanceledException) { throw; } catch (DirectoryNotFoundException) { Log.ErrorFormat("{0} : {1} -> {2} failed because the directory was not found.\n Rescheduling a delete", action.Action, action.LocalFile, action.CloudFile); //Repost a delete action for the missing file _deleteAgent.Post(action); } catch (FileNotFoundException) { Log.ErrorFormat("{0} : {1} -> {2} failed because the file was not found.\n Rescheduling a delete", action.Action, action.LocalFile, action.CloudFile); //Post a delete action for the missing file _deleteAgent.Post(action); } catch (Exception exc) { Log.ErrorFormat("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc); _deleteAgent.Post(action); } finally { //Set the event when all delete actions are processed if (_deleteAgent.InputCount == 0) _proceedEvent.Set(); } } } //Returns true if an action concerns a file that was deleted public bool IsDeletedFile(CloudAction action) { //Doesn't work for actions targeting shared files if (action.IsShared) return false; var key = GetFileKey(action.CloudFile); DateTime entryDate; if (_deletedFiles.TryGetValue(key, out entryDate)) { //If the delete entry was created after this action, abort the action if (entryDate > action.Created) return true; //Otherwise, remove the stale entry _deletedFiles.TryRemove(key, out entryDate); } return false; } public void Post(CloudDeleteAction action) { _deleteAgent.Post(action); } public void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile) { if (accountInfo == null) throw new ArgumentNullException("accountInfo"); if (cloudFile == null) throw new ArgumentNullException("cloudFile"); if (String.IsNullOrWhiteSpace(cloudFile.Container)) throw new ArgumentException("Invalid container", "cloudFile"); Contract.EndContractBlock(); var fileAgent = GetFileAgent(accountInfo); using (ThreadContext.Stacks["Operation"].Push("DeleteCloudFile")) { var fileName = cloudFile.RelativeUrlToFilePath(accountInfo.UserName); var info = fileAgent.GetFileSystemInfo(fileName); var fullPath = info.FullName.ToLower(); StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified).Wait(); var account = cloudFile.Account ?? accountInfo.UserName; var container = cloudFile.Container;//?? FolderConstants.PithosContainer; var client = new CloudFilesClient(accountInfo); client.DeleteObject(account, container, cloudFile.Name); StatusKeeper.ClearFileStatus(fullPath); StatusNotification.Notify(new CloudNotification{Data=cloudFile}); } } private static string GetFileKey(ObjectInfo info) { var key = String.Format("{0}/{1}/{2}", info.Account, info.Container, info.Name); return key; } private static FileAgent GetFileAgent(AccountInfo accountInfo) { return AgentLocator.Get(accountInfo.AccountPath); } } }