Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / DeleteAgent.cs @ 70e0b702

History | View | Annotate | Download (9.9 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
        public IStatusNotification StatusNotification { get; set; }
70

    
71
        //A separate agent is used to execute delete actions immediatelly;
72
        private readonly ActionBlock<CloudDeleteAction> _deleteAgent;
73

    
74
        //The Proceed event signals the the network agent to proceed with processing.
75
        //Essentially this event pauses the network agent to give priority to the deletion agent
76
        //Initially the event is signalled because we don't need to pause
77
        private readonly AsyncManualResetEvent _proceedEvent = new AsyncManualResetEvent(true);
78

    
79
        public AsyncManualResetEvent ProceedEvent
80
        {
81
            get { return _proceedEvent; }
82
        }
83

    
84
        //Deleted file names are stored in memory so we can check that a file has already been deleted.
85
        //and avoid sending duplicate delete commands
86
        readonly ConcurrentDictionary<string, DateTime> _deletedFiles = new ConcurrentDictionary<string, DateTime>();
87

    
88
        public DeleteAgent()
89
        {
90
            _deleteAgent = new ActionBlock<CloudDeleteAction>(message => ProcessDelete(message), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
91

    
92
        }
93

    
94
        /// <summary>
95
        /// Processes cloud delete actions
96
        /// </summary>
97
        /// <param name="action">The delete action to execute</param>
98
        /// <returns></returns>
99
        /// <remarks>
100
        /// When a file/folder is deleted locally, we must delete it ASAP from the server and block any download
101
        /// operations that may be in progress.
102
        /// <para>
103
        /// A separate agent is used to process deletes because the main agent may be busy with a long operation.
104
        /// </para>
105
        /// </remarks>
106
        private void ProcessDelete(CloudDeleteAction action)
107
        {
108
            if (action == null)
109
                throw new ArgumentNullException("action");
110
            if (action.AccountInfo == null)
111
                throw new ArgumentException("The action.AccountInfo is empty", "action");
112
            Contract.EndContractBlock();
113

    
114
            var accountInfo = action.AccountInfo;
115

    
116
            using (log4net.ThreadContext.Stacks["Operation"].Push("ProcessDelete"))
117
            {
118
                Log.InfoFormat("[ACTION] Start Processing {0}", action);
119

    
120
                var cloudFile = action.CloudFile;
121

    
122
                try
123
                {
124
                    //Acquire a lock on the deleted file to prevent uploading/downloading operations from the normal
125
                    //agent
126
                    using (NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting))
127
                    {
128

    
129
                        //Add the file URL to the deleted files list
130
                        var key = GetFileKey(action.CloudFile);
131
                        _deletedFiles[key] = DateTime.Now;
132

    
133
                        _proceedEvent.Reset();
134
                        // and then delete the file from the server
135
                        DeleteCloudFile(accountInfo, cloudFile);
136

    
137
                        Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile,
138
                                       action.CloudFile.Name);
139
                    }
140
                }
141
                catch (WebException exc)
142
                {
143
                    Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc);
144
                }
145
                catch (OperationCanceledException)
146
                {
147
                    throw;
148
                }
149
                catch (DirectoryNotFoundException)
150
                {
151
                    Log.ErrorFormat("{0} : {1} -> {2}  failed because the directory was not found.\n Rescheduling a delete",
152
                        action.Action, action.LocalFile, action.CloudFile);
153
                    //Repost a delete action for the missing file
154
                    _deleteAgent.Post(action);
155
                }
156
                catch (FileNotFoundException)
157
                {
158
                    Log.ErrorFormat("{0} : {1} -> {2}  failed because the file was not found.\n Rescheduling a delete",
159
                        action.Action, action.LocalFile, action.CloudFile);
160
                    //Post a delete action for the missing file
161
                    _deleteAgent.Post(action);
162
                }
163
                catch (Exception exc)
164
                {
165
                    Log.ErrorFormat("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}",
166
                                     action.Action, action.LocalFile, action.CloudFile, exc);
167

    
168
                    _deleteAgent.Post(action);
169
                }
170
                finally
171
                {
172
                    //Set the event when all delete actions are processed
173
                    if (_deleteAgent.InputCount == 0)
174
                        _proceedEvent.Set();
175

    
176
                }
177
            }
178
        }
179

    
180
        //Returns true if an action concerns a file that was deleted
181
        public bool IsDeletedFile(CloudAction action)
182
        {
183
            //Doesn't work for actions targeting shared files
184
            if (action.IsShared)
185
                return false;
186
            var key = GetFileKey(action.CloudFile);
187
            DateTime entryDate;
188
            if (_deletedFiles.TryGetValue(key, out entryDate))
189
            {
190
                //If the delete entry was created after this action, abort the action
191
                if (entryDate > action.Created)
192
                    return true;
193
                //Otherwise, remove the stale entry 
194
                _deletedFiles.TryRemove(key, out entryDate);
195
            }
196
            return false;
197
        }
198

    
199
        public void Post(CloudDeleteAction action)
200
        {
201
            _deleteAgent.Post(action);
202
        }
203

    
204
        private void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile)
205
        {
206
            if (accountInfo == null)
207
                throw new ArgumentNullException("accountInfo");
208
            if (cloudFile == null)
209
                throw new ArgumentNullException("cloudFile");
210

    
211
            if (String.IsNullOrWhiteSpace(cloudFile.Container))
212
                throw new ArgumentException("Invalid container", "cloudFile");
213
            Contract.EndContractBlock();
214

    
215
            var fileAgent = GetFileAgent(accountInfo);
216

    
217
            using (ThreadContext.Stacks["Operation"].Push("DeleteCloudFile"))
218
            {
219
                var fileName = cloudFile.RelativeUrlToFilePath(accountInfo.UserName);
220
                var info = fileAgent.GetFileSystemInfo(fileName);
221
                var fullPath = info.FullName.ToLower();
222

    
223
                StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified);
224

    
225
                var account = cloudFile.Account ?? accountInfo.UserName;
226
                var container = cloudFile.Container;//?? FolderConstants.PithosContainer;
227

    
228
                var client = new CloudFilesClient(accountInfo);
229
                client.DeleteObject(account, container, cloudFile.Name);
230

    
231
                StatusKeeper.ClearFileStatus(fullPath);
232
                StatusNotification.Notify(new CloudNotification{Data=cloudFile});
233
            }
234
        }
235

    
236

    
237
        private static string GetFileKey(ObjectInfo info)
238
        {
239
            var key = String.Format("{0}/{1}/{2}", info.Account, info.Container, info.Name);
240
            return key;
241
        }
242

    
243
        private static FileAgent GetFileAgent(AccountInfo accountInfo)
244
        {
245
            return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);
246
        }
247

    
248
    }
249
}