Statistics
| Branch: | Revision:

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
}