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 public IStatusNotification StatusNotification { get; set; }
71 //A separate agent is used to execute delete actions immediatelly;
72 private readonly ActionBlock<CloudDeleteAction> _deleteAgent;
74 //The Proceed event signals the the network agent to proceed with processing.
75 //Essentially this event pauses the network agent to give priority to the deletion agent
76 //Initially the event is signalled because we don't need to pause
77 private readonly AsyncManualResetEvent _proceedEvent = new AsyncManualResetEvent(true);
79 public AsyncManualResetEvent ProceedEvent
81 get { return _proceedEvent; }
84 //Deleted file names are stored in memory so we can check that a file has already been deleted.
85 //and avoid sending duplicate delete commands
86 readonly ConcurrentDictionary<string, DateTime> _deletedFiles = new ConcurrentDictionary<string, DateTime>();
90 _deleteAgent = new ActionBlock<CloudDeleteAction>(message => ProcessDelete(message), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
95 /// Processes cloud delete actions
97 /// <param name="action">The delete action to execute</param>
98 /// <returns></returns>
100 /// When a file/folder is deleted locally, we must delete it ASAP from the server and block any download
101 /// operations that may be in progress.
103 /// A separate agent is used to process deletes because the main agent may be busy with a long operation.
106 private void ProcessDelete(CloudDeleteAction action)
109 throw new ArgumentNullException("action");
110 if (action.AccountInfo == null)
111 throw new ArgumentException("The action.AccountInfo is empty", "action");
112 Contract.EndContractBlock();
114 var accountInfo = action.AccountInfo;
116 using (log4net.ThreadContext.Stacks["Operation"].Push("ProcessDelete"))
118 Log.InfoFormat("[ACTION] Start Processing {0}", action);
120 var cloudFile = action.CloudFile;
124 //Acquire a lock on the deleted file to prevent uploading/downloading operations from the normal
126 using (NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting))
129 //Add the file URL to the deleted files list
130 var key = GetFileKey(action.CloudFile);
131 _deletedFiles[key] = DateTime.Now;
133 _proceedEvent.Reset();
134 // and then delete the file from the server
135 DeleteCloudFile(accountInfo, cloudFile);
137 Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile,
138 action.CloudFile.Name);
141 catch (WebException exc)
143 Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc);
145 catch (OperationCanceledException)
149 catch (DirectoryNotFoundException)
151 Log.ErrorFormat("{0} : {1} -> {2} failed because the directory was not found.\n Rescheduling a delete",
152 action.Action, action.LocalFile, action.CloudFile);
153 //Repost a delete action for the missing file
154 _deleteAgent.Post(action);
156 catch (FileNotFoundException)
158 Log.ErrorFormat("{0} : {1} -> {2} failed because the file was not found.\n Rescheduling a delete",
159 action.Action, action.LocalFile, action.CloudFile);
160 //Post a delete action for the missing file
161 _deleteAgent.Post(action);
163 catch (Exception exc)
165 Log.ErrorFormat("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}",
166 action.Action, action.LocalFile, action.CloudFile, exc);
168 _deleteAgent.Post(action);
172 //Set the event when all delete actions are processed
173 if (_deleteAgent.InputCount == 0)
180 //Returns true if an action concerns a file that was deleted
181 public bool IsDeletedFile(CloudAction action)
183 //Doesn't work for actions targeting shared files
186 var key = GetFileKey(action.CloudFile);
188 if (_deletedFiles.TryGetValue(key, out entryDate))
190 //If the delete entry was created after this action, abort the action
191 if (entryDate > action.Created)
193 //Otherwise, remove the stale entry
194 _deletedFiles.TryRemove(key, out entryDate);
199 public void Post(CloudDeleteAction action)
201 _deleteAgent.Post(action);
204 private void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile)
206 if (accountInfo == null)
207 throw new ArgumentNullException("accountInfo");
208 if (cloudFile == null)
209 throw new ArgumentNullException("cloudFile");
211 if (String.IsNullOrWhiteSpace(cloudFile.Container))
212 throw new ArgumentException("Invalid container", "cloudFile");
213 Contract.EndContractBlock();
215 var fileAgent = GetFileAgent(accountInfo);
217 using (ThreadContext.Stacks["Operation"].Push("DeleteCloudFile"))
219 var fileName = cloudFile.RelativeUrlToFilePath(accountInfo.UserName);
220 var info = fileAgent.GetFileSystemInfo(fileName);
221 var fullPath = info.FullName.ToLower();
223 StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified);
225 var account = cloudFile.Account ?? accountInfo.UserName;
226 var container = cloudFile.Container;//?? FolderConstants.PithosContainer;
228 var client = new CloudFilesClient(accountInfo);
229 client.DeleteObject(account, container, cloudFile.Name);
231 StatusKeeper.ClearFileStatus(fullPath);
232 StatusNotification.Notify(new CloudNotification{Data=cloudFile});
237 private static string GetFileKey(ObjectInfo info)
239 var key = String.Format("{0}/{1}/{2}", info.Account, info.Container, info.Name);
243 private static FileAgent GetFileAgent(AccountInfo accountInfo)
245 return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);