Modified loggers to use their enclosing class
[pithos-ms-client] / trunk / Pithos.Core / Agents / DeleteAgent.cs
1 #region
2 /* -----------------------------------------------------------------------
3  * <copyright file="DeleteAgent.cs" company="GRNet">
4  * 
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or
8  * without modification, are permitted provided that the following
9  * conditions are met:
10  *
11  *   1. Redistributions of source code must retain the above
12  *      copyright notice, this list of conditions and the following
13  *      disclaimer.
14  *
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.
19  *
20  *
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.
33  *
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.
38  * </copyright>
39  * -----------------------------------------------------------------------
40  */
41 #endregion
42 using System.Collections.Concurrent;
43 using System.ComponentModel.Composition;
44 using System.Diagnostics.Contracts;
45 using System.IO;
46 using System.Net;
47 using System.Reflection;
48 using System.Threading.Tasks.Dataflow;
49 using Pithos.Interfaces;
50 using Pithos.Network;
51 using System;
52 using log4net;
53
54 namespace Pithos.Core.Agents
55 {
56
57     /// <summary>
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
60     /// </summary>    
61     [Export]
62     public class DeleteAgent
63     {
64         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
65
66         [Import]
67         public IStatusKeeper StatusKeeper { get; set; }
68
69         //A separate agent is used to execute delete actions immediatelly;
70         private readonly ActionBlock<CloudDeleteAction> _deleteAgent;
71
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);
76
77         public AsyncManualResetEvent ProceedEvent
78         {
79             get { return _proceedEvent; }
80         }
81
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>();
85
86         public DeleteAgent()
87         {
88             _deleteAgent = new ActionBlock<CloudDeleteAction>(message => ProcessDelete(message), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
89
90         }
91
92         /// <summary>
93         /// Processes cloud delete actions
94         /// </summary>
95         /// <param name="action">The delete action to execute</param>
96         /// <returns></returns>
97         /// <remarks>
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.
100         /// <para>
101         /// A separate agent is used to process deletes because the main agent may be busy with a long operation.
102         /// </para>
103         /// </remarks>
104         private void ProcessDelete(CloudDeleteAction action)
105         {
106             if (action == null)
107                 throw new ArgumentNullException("action");
108             if (action.AccountInfo == null)
109                 throw new ArgumentException("The action.AccountInfo is empty", "action");
110             Contract.EndContractBlock();
111
112             var accountInfo = action.AccountInfo;
113
114             using (log4net.ThreadContext.Stacks["NETWORK"].Push("PROCESS"))
115             {
116                 Log.InfoFormat("[ACTION] Start Processing {0}", action);
117
118                 var cloudFile = action.CloudFile;
119
120                 try
121                 {
122                     //Acquire a lock on the deleted file to prevent uploading/downloading operations from the normal
123                     //agent
124                     using (NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting))
125                     {
126
127                         //Add the file URL to the deleted files list
128                         var key = GetFileKey(action.CloudFile);
129                         _deletedFiles[key] = DateTime.Now;
130
131                         _proceedEvent.Reset();
132                         // and then delete the file from the server
133                         DeleteCloudFile(accountInfo, cloudFile);
134
135                         Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile,
136                                        action.CloudFile.Name);
137                     }
138                 }
139                 catch (WebException exc)
140                 {
141                     Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc);
142                 }
143                 catch (OperationCanceledException)
144                 {
145                     throw;
146                 }
147                 catch (DirectoryNotFoundException)
148                 {
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);
153                 }
154                 catch (FileNotFoundException)
155                 {
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);
160                 }
161                 catch (Exception exc)
162                 {
163                     Log.ErrorFormat("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}",
164                                      action.Action, action.LocalFile, action.CloudFile, exc);
165
166                     _deleteAgent.Post(action);
167                 }
168                 finally
169                 {
170                     //Set the event when all delete actions are processed
171                     if (_deleteAgent.InputCount == 0)
172                         _proceedEvent.Set();
173
174                 }
175             }
176         }
177
178         //Returns true if an action concerns a file that was deleted
179         public bool IsDeletedFile(CloudAction action)
180         {
181             //Doesn't work for actions targeting shared files
182             if (action.IsShared)
183                 return false;
184             var key = GetFileKey(action.CloudFile);
185             DateTime entryDate;
186             if (_deletedFiles.TryGetValue(key, out entryDate))
187             {
188                 //If the delete entry was created after this action, abort the action
189                 if (entryDate > action.Created)
190                     return true;
191                 //Otherwise, remove the stale entry 
192                 _deletedFiles.TryRemove(key, out entryDate);
193             }
194             return false;
195         }
196
197         public void Post(CloudDeleteAction action)
198         {
199             _deleteAgent.Post(action);
200         }
201
202         private void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile)
203         {
204             if (accountInfo == null)
205                 throw new ArgumentNullException("accountInfo");
206             if (cloudFile == null)
207                 throw new ArgumentNullException("cloudFile");
208
209             if (String.IsNullOrWhiteSpace(cloudFile.Container))
210                 throw new ArgumentException("Invalid container", "cloudFile");
211             Contract.EndContractBlock();
212
213             var fileAgent = GetFileAgent(accountInfo);
214
215             using (ThreadContext.Stacks["DeleteCloudFile"].Push("Delete"))
216             {
217                 var fileName = cloudFile.RelativeUrlToFilePath(accountInfo.UserName);
218                 var info = fileAgent.GetFileSystemInfo(fileName);
219                 var fullPath = info.FullName.ToLower();
220
221                 StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified);
222
223                 var account = cloudFile.Account ?? accountInfo.UserName;
224                 var container = cloudFile.Container;//?? FolderConstants.PithosContainer;
225
226                 var client = new CloudFilesClient(accountInfo);
227                 client.DeleteObject(account, container, cloudFile.Name);
228
229                 StatusKeeper.ClearFileStatus(fullPath);
230             }
231         }
232
233
234         private static string GetFileKey(ObjectInfo info)
235         {
236             var key = String.Format("{0}/{1}/{2}", info.Account, info.Container, info.Name);
237             return key;
238         }
239
240         private static FileAgent GetFileAgent(AccountInfo accountInfo)
241         {
242             return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);
243         }
244
245     }
246 }