#region
/* -----------------------------------------------------------------------
*
*
* Copyright 2011-2012 GRNET S.A. All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
*
* THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and
* documentation are those of the authors and should not be
* interpreted as representing official policies, either expressed
* or implied, of GRNET S.A.
*
* -----------------------------------------------------------------------
*/
#endregion
using System.Collections.Concurrent;
using System.ComponentModel.Composition;
using System.Diagnostics.Contracts;
using System.IO;
using System.Net;
using System.Reflection;
using System.Threading.Tasks.Dataflow;
using Pithos.Interfaces;
using Pithos.Network;
using System;
using log4net;
namespace Pithos.Core.Agents
{
///
/// The Delete Agent is used to delete files from the Pithos server with high priority,
/// blocking the network agent through the ProceedEvent until all pending deletions complete
///
[Export]
public class DeleteAgent
{
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
[Import]
public IStatusKeeper StatusKeeper { get; set; }
public IStatusNotification StatusNotification { get; set; }
//A separate agent is used to execute delete actions immediatelly;
private readonly ActionBlock _deleteAgent;
//The Proceed event signals the the network agent to proceed with processing.
//Essentially this event pauses the network agent to give priority to the deletion agent
//Initially the event is signalled because we don't need to pause
private readonly AsyncManualResetEvent _proceedEvent = new AsyncManualResetEvent(true);
public AsyncManualResetEvent ProceedEvent
{
get { return _proceedEvent; }
}
//Deleted file names are stored in memory so we can check that a file has already been deleted.
//and avoid sending duplicate delete commands
readonly ConcurrentDictionary _deletedFiles = new ConcurrentDictionary();
public DeleteAgent()
{
_deleteAgent = new ActionBlock(message => ProcessDelete(message), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
}
///
/// Processes cloud delete actions
///
/// The delete action to execute
///
///
/// When a file/folder is deleted locally, we must delete it ASAP from the server and block any download
/// operations that may be in progress.
///
/// A separate agent is used to process deletes because the main agent may be busy with a long operation.
///
///
private void ProcessDelete(CloudDeleteAction action)
{
if (action == null)
throw new ArgumentNullException("action");
if (action.AccountInfo == null)
throw new ArgumentException("The action.AccountInfo is empty", "action");
Contract.EndContractBlock();
var accountInfo = action.AccountInfo;
using (log4net.ThreadContext.Stacks["Operation"].Push("ProcessDelete"))
{
Log.InfoFormat("[ACTION] Start Processing {0}", action);
var cloudFile = action.CloudFile;
try
{
//Acquire a lock on the deleted file to prevent uploading/downloading operations from the normal
//agent
using (NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting))
{
//Add the file URL to the deleted files list
var key = GetFileKey(action.CloudFile);
_deletedFiles[key] = DateTime.Now;
_proceedEvent.Reset();
// and then delete the file from the server
DeleteCloudFile(accountInfo, cloudFile);
Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile,
action.CloudFile.Name);
}
}
catch (WebException exc)
{
Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc);
}
catch (OperationCanceledException)
{
throw;
}
catch (DirectoryNotFoundException)
{
Log.ErrorFormat("{0} : {1} -> {2} failed because the directory was not found.\n Rescheduling a delete",
action.Action, action.LocalFile, action.CloudFile);
//Repost a delete action for the missing file
_deleteAgent.Post(action);
}
catch (FileNotFoundException)
{
Log.ErrorFormat("{0} : {1} -> {2} failed because the file was not found.\n Rescheduling a delete",
action.Action, action.LocalFile, action.CloudFile);
//Post a delete action for the missing file
_deleteAgent.Post(action);
}
catch (Exception exc)
{
Log.ErrorFormat("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}",
action.Action, action.LocalFile, action.CloudFile, exc);
_deleteAgent.Post(action);
}
finally
{
//Set the event when all delete actions are processed
if (_deleteAgent.InputCount == 0)
_proceedEvent.Set();
}
}
}
//Returns true if an action concerns a file that was deleted
public bool IsDeletedFile(CloudAction action)
{
//Doesn't work for actions targeting shared files
if (action.IsShared)
return false;
var key = GetFileKey(action.CloudFile);
DateTime entryDate;
if (_deletedFiles.TryGetValue(key, out entryDate))
{
//If the delete entry was created after this action, abort the action
if (entryDate > action.Created)
return true;
//Otherwise, remove the stale entry
_deletedFiles.TryRemove(key, out entryDate);
}
return false;
}
public void Post(CloudDeleteAction action)
{
_deleteAgent.Post(action);
}
public void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile)
{
if (accountInfo == null)
throw new ArgumentNullException("accountInfo");
if (cloudFile == null)
throw new ArgumentNullException("cloudFile");
if (String.IsNullOrWhiteSpace(cloudFile.Container))
throw new ArgumentException("Invalid container", "cloudFile");
Contract.EndContractBlock();
var fileAgent = GetFileAgent(accountInfo);
using (ThreadContext.Stacks["Operation"].Push("DeleteCloudFile"))
{
var fileName = cloudFile.RelativeUrlToFilePath(accountInfo.UserName);
var info = fileAgent.GetFileSystemInfo(fileName);
var fullPath = info.FullName.ToLower();
StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified).Wait();
var account = cloudFile.Account ?? accountInfo.UserName;
var container = cloudFile.Container;//?? FolderConstants.PithosContainer;
var client = new CloudFilesClient(accountInfo);
client.DeleteObject(account, container, cloudFile.Name);
StatusKeeper.ClearFileStatus(fullPath);
StatusNotification.Notify(new CloudNotification{Data=cloudFile});
}
}
private static string GetFileKey(ObjectInfo info)
{
var key = String.Format("{0}/{1}/{2}", info.Account, info.Container, info.Name);
return key;
}
private static FileAgent GetFileAgent(AccountInfo accountInfo)
{
return AgentLocator.Get(accountInfo.AccountPath);
}
}
}