root / trunk / Pithos.Core / Agents / DeleteAgent.cs @ 2f5fcd2f
History | View | Annotate | Download (10.5 kB)
1 | d7288179 | pkanavos | #region |
---|---|---|---|
2 | d7288179 | pkanavos | /* ----------------------------------------------------------------------- |
3 | d7288179 | pkanavos | * <copyright file="DeleteAgent.cs" company="GRNet"> |
4 | d7288179 | pkanavos | * |
5 | d7288179 | pkanavos | * Copyright 2011-2012 GRNET S.A. All rights reserved. |
6 | d7288179 | pkanavos | * |
7 | d7288179 | pkanavos | * Redistribution and use in source and binary forms, with or |
8 | d7288179 | pkanavos | * without modification, are permitted provided that the following |
9 | d7288179 | pkanavos | * conditions are met: |
10 | d7288179 | pkanavos | * |
11 | d7288179 | pkanavos | * 1. Redistributions of source code must retain the above |
12 | d7288179 | pkanavos | * copyright notice, this list of conditions and the following |
13 | d7288179 | pkanavos | * disclaimer. |
14 | d7288179 | pkanavos | * |
15 | d7288179 | pkanavos | * 2. Redistributions in binary form must reproduce the above |
16 | d7288179 | pkanavos | * copyright notice, this list of conditions and the following |
17 | d7288179 | pkanavos | * disclaimer in the documentation and/or other materials |
18 | d7288179 | pkanavos | * provided with the distribution. |
19 | d7288179 | pkanavos | * |
20 | d7288179 | pkanavos | * |
21 | d7288179 | pkanavos | * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
22 | d7288179 | pkanavos | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
23 | d7288179 | pkanavos | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
24 | d7288179 | pkanavos | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
25 | d7288179 | pkanavos | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
26 | d7288179 | pkanavos | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
27 | d7288179 | pkanavos | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
28 | d7288179 | pkanavos | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
29 | d7288179 | pkanavos | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
30 | d7288179 | pkanavos | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
31 | d7288179 | pkanavos | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
32 | d7288179 | pkanavos | * POSSIBILITY OF SUCH DAMAGE. |
33 | d7288179 | pkanavos | * |
34 | d7288179 | pkanavos | * The views and conclusions contained in the software and |
35 | d7288179 | pkanavos | * documentation are those of the authors and should not be |
36 | d7288179 | pkanavos | * interpreted as representing official policies, either expressed |
37 | d7288179 | pkanavos | * or implied, of GRNET S.A. |
38 | d7288179 | pkanavos | * </copyright> |
39 | d7288179 | pkanavos | * ----------------------------------------------------------------------- |
40 | d7288179 | pkanavos | */ |
41 | d7288179 | pkanavos | #endregion |
42 | d7288179 | pkanavos | using System.Collections.Concurrent; |
43 | d7288179 | pkanavos | using System.ComponentModel.Composition; |
44 | d7288179 | pkanavos | using System.Diagnostics.Contracts; |
45 | d7288179 | pkanavos | using System.IO; |
46 | d7288179 | pkanavos | using System.Net; |
47 | d7288179 | pkanavos | using System.Reflection; |
48 | d7288179 | pkanavos | using System.Threading.Tasks.Dataflow; |
49 | d7288179 | pkanavos | using Pithos.Interfaces; |
50 | d7288179 | pkanavos | using Pithos.Network; |
51 | d7288179 | pkanavos | using System; |
52 | d7288179 | pkanavos | using log4net; |
53 | d7288179 | pkanavos | |
54 | d7288179 | pkanavos | namespace Pithos.Core.Agents |
55 | d7288179 | pkanavos | { |
56 | d7288179 | pkanavos | |
57 | d7288179 | pkanavos | /// <summary> |
58 | d7288179 | pkanavos | /// The Delete Agent is used to delete files from the Pithos server with high priority, |
59 | d7288179 | pkanavos | /// blocking the network agent through the ProceedEvent until all pending deletions complete |
60 | d7288179 | pkanavos | /// </summary> |
61 | d7288179 | pkanavos | [Export] |
62 | d7288179 | pkanavos | public class DeleteAgent |
63 | d7288179 | pkanavos | { |
64 | d7288179 | pkanavos | private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
65 | d7288179 | pkanavos | |
66 | d7288179 | pkanavos | [Import] |
67 | d7288179 | pkanavos | public IStatusKeeper StatusKeeper { get; set; } |
68 | d7288179 | pkanavos | |
69 | d7288179 | pkanavos | public IStatusNotification StatusNotification { get; set; } |
70 | d7288179 | pkanavos | |
71 | d7288179 | pkanavos | //A separate agent is used to execute delete actions immediatelly; |
72 | d7288179 | pkanavos | private readonly ActionBlock<CloudDeleteAction> _deleteAgent; |
73 | d7288179 | pkanavos | |
74 | d7288179 | pkanavos | //The Proceed event signals the the network agent to proceed with processing. |
75 | d7288179 | pkanavos | //Essentially this event pauses the network agent to give priority to the deletion agent |
76 | d7288179 | pkanavos | //Initially the event is signalled because we don't need to pause |
77 | d7288179 | pkanavos | private readonly AsyncManualResetEvent _proceedEvent = new AsyncManualResetEvent(true); |
78 | d7288179 | pkanavos | |
79 | d7288179 | pkanavos | public AsyncManualResetEvent ProceedEvent |
80 | d7288179 | pkanavos | { |
81 | d7288179 | pkanavos | get { return _proceedEvent; } |
82 | d7288179 | pkanavos | } |
83 | d7288179 | pkanavos | |
84 | d7288179 | pkanavos | //Deleted file names are stored in memory so we can check that a file has already been deleted. |
85 | d7288179 | pkanavos | //and avoid sending duplicate delete commands |
86 | d7288179 | pkanavos | readonly ConcurrentDictionary<string, DateTime> _deletedFiles = new ConcurrentDictionary<string, DateTime>(); |
87 | d7288179 | pkanavos | |
88 | d7288179 | pkanavos | public DeleteAgent() |
89 | d7288179 | pkanavos | { |
90 | d7288179 | pkanavos | _deleteAgent = new ActionBlock<CloudDeleteAction>(message => ProcessDelete(message), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 }); |
91 | d7288179 | pkanavos | |
92 | d7288179 | pkanavos | } |
93 | d7288179 | pkanavos | |
94 | d7288179 | pkanavos | /// <summary> |
95 | d7288179 | pkanavos | /// Processes cloud delete actions |
96 | d7288179 | pkanavos | /// </summary> |
97 | d7288179 | pkanavos | /// <param name="action">The delete action to execute</param> |
98 | d7288179 | pkanavos | /// <returns></returns> |
99 | d7288179 | pkanavos | /// <remarks> |
100 | d7288179 | pkanavos | /// When a file/folder is deleted locally, we must delete it ASAP from the server and block any download |
101 | d7288179 | pkanavos | /// operations that may be in progress. |
102 | d7288179 | pkanavos | /// <para> |
103 | d7288179 | pkanavos | /// A separate agent is used to process deletes because the main agent may be busy with a long operation. |
104 | d7288179 | pkanavos | /// </para> |
105 | d7288179 | pkanavos | /// </remarks> |
106 | d7288179 | pkanavos | private void ProcessDelete(CloudDeleteAction action) |
107 | d7288179 | pkanavos | { |
108 | d7288179 | pkanavos | if (action == null) |
109 | d7288179 | pkanavos | throw new ArgumentNullException("action"); |
110 | d7288179 | pkanavos | if (action.AccountInfo == null) |
111 | d7288179 | pkanavos | throw new ArgumentException("The action.AccountInfo is empty", "action"); |
112 | d7288179 | pkanavos | Contract.EndContractBlock(); |
113 | d7288179 | pkanavos | |
114 | d7288179 | pkanavos | var accountInfo = action.AccountInfo; |
115 | d7288179 | pkanavos | |
116 | d7288179 | pkanavos | using (log4net.ThreadContext.Stacks["Operation"].Push("ProcessDelete")) |
117 | d7288179 | pkanavos | { |
118 | d7288179 | pkanavos | Log.InfoFormat("[ACTION] Start Processing {0}", action); |
119 | d7288179 | pkanavos | |
120 | d7288179 | pkanavos | var cloudFile = action.CloudFile; |
121 | d7288179 | pkanavos | |
122 | d7288179 | pkanavos | try |
123 | d7288179 | pkanavos | { |
124 | d7288179 | pkanavos | //Acquire a lock on the deleted file to prevent uploading/downloading operations from the normal |
125 | d7288179 | pkanavos | //agent |
126 | d7288179 | pkanavos | using (NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting)) |
127 | d7288179 | pkanavos | { |
128 | d7288179 | pkanavos | |
129 | d7288179 | pkanavos | //Add the file URL to the deleted files list |
130 | d7288179 | pkanavos | var key = GetFileKey(action.CloudFile); |
131 | d7288179 | pkanavos | _deletedFiles[key] = DateTime.Now; |
132 | d7288179 | pkanavos | |
133 | d7288179 | pkanavos | _proceedEvent.Reset(); |
134 | d7288179 | pkanavos | // and then delete the file from the server |
135 | d7288179 | pkanavos | DeleteCloudFile(accountInfo, cloudFile); |
136 | d7288179 | pkanavos | |
137 | d7288179 | pkanavos | Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile, |
138 | d7288179 | pkanavos | action.CloudFile.Name); |
139 | d7288179 | pkanavos | } |
140 | d7288179 | pkanavos | } |
141 | d7288179 | pkanavos | catch (WebException exc) |
142 | d7288179 | pkanavos | { |
143 | d7288179 | pkanavos | Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc); |
144 | d7288179 | pkanavos | } |
145 | d7288179 | pkanavos | catch (OperationCanceledException) |
146 | d7288179 | pkanavos | { |
147 | d7288179 | pkanavos | throw; |
148 | d7288179 | pkanavos | } |
149 | d7288179 | pkanavos | catch (DirectoryNotFoundException) |
150 | d7288179 | pkanavos | { |
151 | d7288179 | pkanavos | Log.ErrorFormat("{0} : {1} -> {2} failed because the directory was not found.\n Rescheduling a delete", |
152 | d7288179 | pkanavos | action.Action, action.LocalFile, action.CloudFile); |
153 | d7288179 | pkanavos | //Repost a delete action for the missing file |
154 | d7288179 | pkanavos | _deleteAgent.Post(action); |
155 | d7288179 | pkanavos | } |
156 | d7288179 | pkanavos | catch (FileNotFoundException) |
157 | d7288179 | pkanavos | { |
158 | d7288179 | pkanavos | Log.ErrorFormat("{0} : {1} -> {2} failed because the file was not found.\n Rescheduling a delete", |
159 | d7288179 | pkanavos | action.Action, action.LocalFile, action.CloudFile); |
160 | d7288179 | pkanavos | //Post a delete action for the missing file |
161 | d7288179 | pkanavos | _deleteAgent.Post(action); |
162 | d7288179 | pkanavos | } |
163 | d7288179 | pkanavos | catch (Exception exc) |
164 | d7288179 | pkanavos | { |
165 | d7288179 | pkanavos | Log.ErrorFormat("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}", |
166 | d7288179 | pkanavos | action.Action, action.LocalFile, action.CloudFile, exc); |
167 | d7288179 | pkanavos | |
168 | d7288179 | pkanavos | _deleteAgent.Post(action); |
169 | d7288179 | pkanavos | } |
170 | d7288179 | pkanavos | finally |
171 | d7288179 | pkanavos | { |
172 | d7288179 | pkanavos | //Set the event when all delete actions are processed |
173 | d7288179 | pkanavos | if (_deleteAgent.InputCount == 0) |
174 | d7288179 | pkanavos | _proceedEvent.Set(); |
175 | d7288179 | pkanavos | |
176 | d7288179 | pkanavos | } |
177 | d7288179 | pkanavos | } |
178 | d7288179 | pkanavos | } |
179 | d7288179 | pkanavos | |
180 | d7288179 | pkanavos | //Returns true if an action concerns a file that was deleted |
181 | d7288179 | pkanavos | public bool IsDeletedFile(CloudAction action) |
182 | d7288179 | pkanavos | { |
183 | d7288179 | pkanavos | //Doesn't work for actions targeting shared files |
184 | d7288179 | pkanavos | if (action.IsShared) |
185 | d7288179 | pkanavos | return false; |
186 | d7288179 | pkanavos | var key = GetFileKey(action.CloudFile); |
187 | d7288179 | pkanavos | DateTime entryDate; |
188 | d7288179 | pkanavos | if (_deletedFiles.TryGetValue(key, out entryDate)) |
189 | d7288179 | pkanavos | { |
190 | d7288179 | pkanavos | //If the delete entry was created after this action, abort the action |
191 | d7288179 | pkanavos | if (entryDate > action.Created) |
192 | d7288179 | pkanavos | return true; |
193 | d7288179 | pkanavos | //Otherwise, remove the stale entry |
194 | d7288179 | pkanavos | _deletedFiles.TryRemove(key, out entryDate); |
195 | d7288179 | pkanavos | } |
196 | d7288179 | pkanavos | return false; |
197 | d7288179 | pkanavos | } |
198 | d7288179 | pkanavos | |
199 | d7288179 | pkanavos | public void Post(CloudDeleteAction action) |
200 | d7288179 | pkanavos | { |
201 | d7288179 | pkanavos | _deleteAgent.Post(action); |
202 | d7288179 | pkanavos | } |
203 | d7288179 | pkanavos | |
204 | cfb09103 | pkanavos | public async void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile) |
205 | d7288179 | pkanavos | { |
206 | d7288179 | pkanavos | if (accountInfo == null) |
207 | d7288179 | pkanavos | throw new ArgumentNullException("accountInfo"); |
208 | d7288179 | pkanavos | if (cloudFile == null) |
209 | d7288179 | pkanavos | throw new ArgumentNullException("cloudFile"); |
210 | d7288179 | pkanavos | |
211 | d7288179 | pkanavos | if (cloudFile.Container==null) |
212 | d7288179 | pkanavos | throw new ArgumentException("Invalid container", "cloudFile"); |
213 | d7288179 | pkanavos | if (cloudFile.Container.IsAbsoluteUri) |
214 | d7288179 | pkanavos | throw new ArgumentException("Invalid container", "cloudFile"); |
215 | d7288179 | pkanavos | Contract.EndContractBlock(); |
216 | d7288179 | pkanavos | |
217 | d7288179 | pkanavos | var fileAgent = GetFileAgent(accountInfo); |
218 | d7288179 | pkanavos | |
219 | d7288179 | pkanavos | using (ThreadContext.Stacks["Operation"].Push("DeleteCloudFile")) |
220 | d7288179 | pkanavos | { |
221 | d7288179 | pkanavos | var fileName = cloudFile.RelativeUrlToFilePath(accountInfo.UserName); |
222 | d7288179 | pkanavos | var info = fileAgent.GetFileSystemInfo(fileName); |
223 | 1ba5e72f | pkanavos | if (info == null) |
224 | 1ba5e72f | pkanavos | { |
225 | 1ba5e72f | pkanavos | Log.WarnFormat("Unable to delete file [{0}] because its path is too long", fileName); |
226 | 1ba5e72f | pkanavos | return; |
227 | 1ba5e72f | pkanavos | } |
228 | 1ba5e72f | pkanavos | |
229 | d7288179 | pkanavos | var fullPath = info.FullName.ToLower(); |
230 | d7288179 | pkanavos | |
231 | 7e39367d | pkanavos | StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified); |
232 | d7288179 | pkanavos | |
233 | d7288179 | pkanavos | var account = cloudFile.Account ?? accountInfo.UserName; |
234 | d7288179 | pkanavos | var container = cloudFile.Container;//?? FolderConstants.PithosContainer; |
235 | d7288179 | pkanavos | |
236 | d7288179 | pkanavos | var client = new CloudFilesClient(accountInfo); |
237 | d7288179 | pkanavos | |
238 | cfb09103 | pkanavos | await client.DeleteObject(account, container, cloudFile.Name,cloudFile.IsDirectory); |
239 | d7288179 | pkanavos | |
240 | d7288179 | pkanavos | StatusKeeper.ClearFileStatus(fullPath); |
241 | d7288179 | pkanavos | StatusNotification.Notify(new CloudNotification{Data=cloudFile}); |
242 | d7288179 | pkanavos | } |
243 | d7288179 | pkanavos | } |
244 | d7288179 | pkanavos | |
245 | d7288179 | pkanavos | |
246 | d7288179 | pkanavos | private static string GetFileKey(ObjectInfo info) |
247 | d7288179 | pkanavos | { |
248 | d7288179 | pkanavos | var key = String.Format("{0}/{1}/{2}", info.Account, info.Container, info.Name); |
249 | d7288179 | pkanavos | return key; |
250 | d7288179 | pkanavos | } |
251 | d7288179 | pkanavos | |
252 | d7288179 | pkanavos | private static FileAgent GetFileAgent(AccountInfo accountInfo) |
253 | d7288179 | pkanavos | { |
254 | d7288179 | pkanavos | return AgentLocator<FileAgent>.Get(accountInfo.AccountPath); |
255 | d7288179 | pkanavos | } |
256 | d7288179 | pkanavos | |
257 | d7288179 | pkanavos | } |
258 | d7288179 | pkanavos | } |