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