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