Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / DeleteAgent.cs @ 255f5f86

History | View | Annotate | Download (9.6 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.Threading.Tasks.Dataflow;
48
using Pithos.Interfaces;
49
using Pithos.Network;
50
using System;
51
using log4net;
52

    
53
namespace Pithos.Core.Agents
54
{
55

    
56
    /// <summary>
57
    /// The Delete Agent is used to delete files from the Pithos server with high priority, 
58
    /// blocking the network agent through the PauseEvent until all pending deletions complete
59
    /// </summary>    
60
    [Export]
61
    public class DeleteAgent
62
    {
63

    
64
        [Import]
65
        public IStatusKeeper StatusKeeper { get; set; }
66

    
67
        private static readonly ILog Log = LogManager.GetLogger("DeleteAgent");
68

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

    
72
        //The Pause event stops the network agent to give priority to the deletion agent
73
        //Initially the event is signalled because we don't need to pause
74
        private readonly AsyncManualResetEvent _pauseEvent = new AsyncManualResetEvent(true);
75

    
76
        public AsyncManualResetEvent PauseEvent
77
        {
78
            get { return _pauseEvent; }
79
        }
80

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

    
85
        public DeleteAgent()
86
        {
87
            _deleteAgent = new ActionBlock<CloudDeleteAction>(message => ProcessDelete(message), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
88

    
89
        }
90

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

    
111
            var accountInfo = action.AccountInfo;
112

    
113
            using (log4net.ThreadContext.Stacks["NETWORK"].Push("PROCESS"))
114
            {
115
                Log.InfoFormat("[ACTION] Start Processing {0}", action);
116

    
117
                var cloudFile = action.CloudFile;
118

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

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

    
130
                        _pauseEvent.Reset();
131
                        // and then delete the file from the server
132
                        DeleteCloudFile(accountInfo, cloudFile);
133

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

    
165
                    _deleteAgent.Post(action);
166
                }
167
                finally
168
                {
169
                    //Set the event when all delete actions are processed
170
                    if (_deleteAgent.InputCount == 0)
171
                        _pauseEvent.Set();
172

    
173
                }
174
            }
175
        }
176

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

    
196
        public void Post(CloudDeleteAction action)
197
        {
198
            _deleteAgent.Post(action);
199
        }
200

    
201
        private void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile)
202
        {
203
            if (accountInfo == null)
204
                throw new ArgumentNullException("accountInfo");
205
            if (cloudFile == null)
206
                throw new ArgumentNullException("cloudFile");
207

    
208
            if (String.IsNullOrWhiteSpace(cloudFile.Container))
209
                throw new ArgumentException("Invalid container", "cloudFile");
210
            Contract.EndContractBlock();
211

    
212
            var fileAgent = GetFileAgent(accountInfo);
213

    
214
            using (ThreadContext.Stacks["DeleteCloudFile"].Push("Delete"))
215
            {
216
                var fileName = cloudFile.RelativeUrlToFilePath(accountInfo.UserName);
217
                var info = fileAgent.GetFileSystemInfo(fileName);
218
                var fullPath = info.FullName.ToLower();
219

    
220
                StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified);
221

    
222
                var account = cloudFile.Account ?? accountInfo.UserName;
223
                var container = cloudFile.Container;//?? FolderConstants.PithosContainer;
224

    
225
                var client = new CloudFilesClient(accountInfo);
226
                client.DeleteObject(account, container, cloudFile.Name);
227

    
228
                StatusKeeper.ClearFileStatus(fullPath);
229
            }
230
        }
231

    
232

    
233
        private static string GetFileKey(ObjectInfo info)
234
        {
235
            var key = String.Format("{0}/{1}/{2}", info.Account, info.Container, info.Name);
236
            return key;
237
        }
238

    
239
        private static FileAgent GetFileAgent(AccountInfo accountInfo)
240
        {
241
            return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);
242
        }
243

    
244
    }
245
}