2 /* -----------------------------------------------------------------------
3 * <copyright file="DeleteAgent.cs" company="GRNet">
5 * Copyright 2011-2012 GRNET S.A. All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
11 * 1. Redistributions of source code must retain the above
12 * copyright notice, this list of conditions and the following
15 * 2. Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials
18 * provided with the distribution.
21 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
34 * The views and conclusions contained in the software and
35 * documentation are those of the authors and should not be
36 * interpreted as representing official policies, either expressed
37 * or implied, of GRNET S.A.
39 * -----------------------------------------------------------------------
42 using System.Collections.Concurrent;
43 using System.ComponentModel.Composition;
44 using System.Diagnostics.Contracts;
47 using System.Reflection;
48 using System.Threading.Tasks.Dataflow;
49 using Pithos.Interfaces;
54 namespace Pithos.Core.Agents
58 /// The Delete Agent is used to delete files from the Pithos server with high priority,
59 /// blocking the network agent through the ProceedEvent until all pending deletions complete
62 public class DeleteAgent
64 private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
67 public IStatusKeeper StatusKeeper { get; set; }
69 //A separate agent is used to execute delete actions immediatelly;
70 private readonly ActionBlock<CloudDeleteAction> _deleteAgent;
72 //The Proceed event signals the the network agent to proceed with processing.
73 //Essentially this event pauses the network agent to give priority to the deletion agent
74 //Initially the event is signalled because we don't need to pause
75 private readonly AsyncManualResetEvent _proceedEvent = new AsyncManualResetEvent(true);
77 public AsyncManualResetEvent ProceedEvent
79 get { return _proceedEvent; }
82 //Deleted file names are stored in memory so we can check that a file has already been deleted.
83 //and avoid sending duplicate delete commands
84 readonly ConcurrentDictionary<string, DateTime> _deletedFiles = new ConcurrentDictionary<string, DateTime>();
88 _deleteAgent = new ActionBlock<CloudDeleteAction>(message => ProcessDelete(message), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
93 /// Processes cloud delete actions
95 /// <param name="action">The delete action to execute</param>
96 /// <returns></returns>
98 /// When a file/folder is deleted locally, we must delete it ASAP from the server and block any download
99 /// operations that may be in progress.
101 /// A separate agent is used to process deletes because the main agent may be busy with a long operation.
104 private void ProcessDelete(CloudDeleteAction action)
107 throw new ArgumentNullException("action");
108 if (action.AccountInfo == null)
109 throw new ArgumentException("The action.AccountInfo is empty", "action");
110 Contract.EndContractBlock();
112 var accountInfo = action.AccountInfo;
114 using (log4net.ThreadContext.Stacks["NETWORK"].Push("PROCESS"))
116 Log.InfoFormat("[ACTION] Start Processing {0}", action);
118 var cloudFile = action.CloudFile;
122 //Acquire a lock on the deleted file to prevent uploading/downloading operations from the normal
124 using (NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting))
127 //Add the file URL to the deleted files list
128 var key = GetFileKey(action.CloudFile);
129 _deletedFiles[key] = DateTime.Now;
131 _proceedEvent.Reset();
132 // and then delete the file from the server
133 DeleteCloudFile(accountInfo, cloudFile);
135 Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile,
136 action.CloudFile.Name);
139 catch (WebException exc)
141 Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc);
143 catch (OperationCanceledException)
147 catch (DirectoryNotFoundException)
149 Log.ErrorFormat("{0} : {1} -> {2} failed because the directory was not found.\n Rescheduling a delete",
150 action.Action, action.LocalFile, action.CloudFile);
151 //Repost a delete action for the missing file
152 _deleteAgent.Post(action);
154 catch (FileNotFoundException)
156 Log.ErrorFormat("{0} : {1} -> {2} failed because the file was not found.\n Rescheduling a delete",
157 action.Action, action.LocalFile, action.CloudFile);
158 //Post a delete action for the missing file
159 _deleteAgent.Post(action);
161 catch (Exception exc)
163 Log.ErrorFormat("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}",
164 action.Action, action.LocalFile, action.CloudFile, exc);
166 _deleteAgent.Post(action);
170 //Set the event when all delete actions are processed
171 if (_deleteAgent.InputCount == 0)
178 //Returns true if an action concerns a file that was deleted
179 public bool IsDeletedFile(CloudAction action)
181 //Doesn't work for actions targeting shared files
184 var key = GetFileKey(action.CloudFile);
186 if (_deletedFiles.TryGetValue(key, out entryDate))
188 //If the delete entry was created after this action, abort the action
189 if (entryDate > action.Created)
191 //Otherwise, remove the stale entry
192 _deletedFiles.TryRemove(key, out entryDate);
197 public void Post(CloudDeleteAction action)
199 _deleteAgent.Post(action);
202 private void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile)
204 if (accountInfo == null)
205 throw new ArgumentNullException("accountInfo");
206 if (cloudFile == null)
207 throw new ArgumentNullException("cloudFile");
209 if (String.IsNullOrWhiteSpace(cloudFile.Container))
210 throw new ArgumentException("Invalid container", "cloudFile");
211 Contract.EndContractBlock();
213 var fileAgent = GetFileAgent(accountInfo);
215 using (ThreadContext.Stacks["DeleteCloudFile"].Push("Delete"))
217 var fileName = cloudFile.RelativeUrlToFilePath(accountInfo.UserName);
218 var info = fileAgent.GetFileSystemInfo(fileName);
219 var fullPath = info.FullName.ToLower();
221 StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified);
223 var account = cloudFile.Account ?? accountInfo.UserName;
224 var container = cloudFile.Container;//?? FolderConstants.PithosContainer;
226 var client = new CloudFilesClient(accountInfo);
227 client.DeleteObject(account, container, cloudFile.Name);
229 StatusKeeper.ClearFileStatus(fullPath);
234 private static string GetFileKey(ObjectInfo info)
236 var key = String.Format("{0}/{1}/{2}", info.Account, info.Container, info.Name);
240 private static FileAgent GetFileAgent(AccountInfo accountInfo)
242 return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);