Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / DeleteAgent.cs @ 6bcdd8e2

History | View | Annotate | Download (9.8 kB)

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["Operation"].Push("ProcessDelete"))
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["Operation"].Push("DeleteCloudFile"))
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
}