Extracted polling functionality to a separate PollAgent.cs
[pithos-ms-client] / trunk / Pithos.Core / Agents / NetworkAgent.cs
index b55c52b..167886b 100644 (file)
@@ -1,39 +1,47 @@
-// -----------------------------------------------------------------------
-// <copyright file="NetworkAgent.cs" company="GRNET">
-// 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.
-// </copyright>
-// -----------------------------------------------------------------------
+#region
+/* -----------------------------------------------------------------------
+ * <copyright file="NetworkAgent.cs" company="GRNet">
+ * 
+ * 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.
+ * </copyright>
+ * -----------------------------------------------------------------------
+ */
+#endregion
+
+//TODO: Now there is a UUID tag. This can be used for renames/moves
+
 
 using System;
 using System.Collections.Concurrent;
@@ -60,12 +68,8 @@ namespace Pithos.Core.Agents
     {
         private Agent<CloudAction> _agent;
 
-        //A separate agent is used to execute delete actions immediatelly;
-        private ActionBlock<CloudDeleteAction> _deleteAgent;
-        readonly ConcurrentDictionary<string,DateTime> _deletedFiles=new ConcurrentDictionary<string, DateTime>();
-
-
-        private readonly ManualResetEventSlim _pauseAgent = new ManualResetEventSlim(true);
+        [System.ComponentModel.Composition.Import]
+        private DeleteAgent _deleteAgent=new DeleteAgent();
 
         [System.ComponentModel.Composition.Import]
         public IStatusKeeper StatusKeeper { get; set; }
@@ -79,19 +83,24 @@ namespace Pithos.Core.Agents
         [System.ComponentModel.Composition.Import]
         public IPithosSettings Settings { get; set; }
 
-        private bool _firstPoll = true;
-        private TaskCompletionSource<bool> _tcs;
-        private ConcurrentDictionary<string,DateTime> _lastSeen=new ConcurrentDictionary<string, DateTime>();
+        //The Pause event stops the poll agent to give priority to the network agent
+        //Initially the event is signalled because we don't need to pause
+        private readonly AsyncManualResetEvent _pauseEvent = new AsyncManualResetEvent(true);
+
+        public AsyncManualResetEvent PauseEvent
+        {
+            get { return _pauseEvent; }
+        }
+
 
         public void Start()
         {
-            _firstPoll = true;
             _agent = Agent<CloudAction>.Start(inbox =>
             {
                 Action loop = null;
                 loop = () =>
                 {
-                    _pauseAgent.Wait();
+                    _deleteAgent.PauseEvent.Wait();
                     var message = inbox.Receive();
                     var process=message.Then(Process,inbox.CancellationToken);
                     inbox.LoopAsync(process, loop);
@@ -99,18 +108,6 @@ namespace Pithos.Core.Agents
                 loop();
             });
 
-            _deleteAgent = new ActionBlock<CloudDeleteAction>(message =>ProcessDelete(message),new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=4});
-            /*
-                Action loop = null;
-                loop = () =>
-                            {
-                                var message = inbox.Receive();
-                                var process = message.Then(ProcessDelete,inbox.CancellationToken);
-                                inbox.LoopAsync(process, loop);
-                            };
-                loop();
-*/
-
         }
 
         private async Task Process(CloudAction action)
@@ -121,8 +118,8 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("The action.AccountInfo is empty","action");
             Contract.EndContractBlock();
 
-            UpdateStatus(PithosStatus.Syncing);
-            var accountInfo = action.AccountInfo;
+
+
 
             using (log4net.ThreadContext.Stacks["NETWORK"].Push("PROCESS"))
             {                
@@ -132,13 +129,17 @@ namespace Pithos.Core.Agents
                 var downloadPath = action.GetDownloadPath();
 
                 try
-                {                    
+                {
+                    _pauseEvent.Reset();
+                    UpdateStatus(PithosStatus.Syncing);
+                    var accountInfo = action.AccountInfo;
+
                     if (action.Action == CloudActionType.DeleteCloud)
                     {                        
                         //Redirect deletes to the delete agent 
                         _deleteAgent.Post((CloudDeleteAction)action);
                     }
-                    if (IsDeletedFile(action))
+                    if (_deleteAgent.IsDeletedFile(action))
                     {
                         //Clear the status of already deleted files to avoid reprocessing
                         if (action.LocalFile != null)
@@ -205,7 +206,9 @@ namespace Pithos.Core.Agents
                 }
                 finally
                 {
-                    UpdateStatus(PithosStatus.InSynch);                    
+                    if (_agent.IsEmpty)
+                        _pauseEvent.Set();
+                    UpdateStatus(PithosStatus.InSynch);                                        
                 }
             }
         }
@@ -216,98 +219,7 @@ namespace Pithos.Core.Agents
             StatusNotification.Notify(new Notification());
         }
 
-        /// <summary>
-        /// Processes cloud delete actions
-        /// </summary>
-        /// <param name="action">The delete action to execute</param>
-        /// <returns></returns>
-        /// <remarks>
-        /// 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.
-        /// <para>
-        /// A separate agent is used to process deletes because the main agent may be busy with a long operation.
-        /// </para>
-        /// </remarks>
-        private async Task 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["NETWORK"].Push("PROCESS"))
-            {                
-                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 (var gate = 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;
-
-                        _pauseAgent.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)
-                        _pauseAgent.Set();
-
-                }
-            }
-        }
-
-        private static string GetFileKey(ObjectInfo info)
-        {
-            var key = String.Format("{0}/{1}/{2}", info.Account, info.Container,info.Name);
-            return key;
-        }
-
+        
         private async Task SyncFiles(AccountInfo accountInfo,CloudAction action)
         {
             if (accountInfo == null)
@@ -402,7 +314,7 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("The CloudAction.AccountInfo is empty","cloudAction");
             Contract.EndContractBlock();
 
-            _pauseAgent.Wait();
+            _deleteAgent.PauseEvent.Wait();
 
             //If the action targets a local file, add a treehash calculation
             if (!(cloudAction is CloudDeleteAction) && cloudAction.LocalFile as FileInfo != null)
@@ -431,476 +343,27 @@ namespace Pithos.Core.Agents
             else
                 _agent.Post(cloudAction);
         }
+       
 
-       /* class ObjectInfoByNameComparer:IEqualityComparer<ObjectInfo>
+        public IEnumerable<CloudAction> GetEnumerable()
         {
-            public bool Equals(ObjectInfo x, ObjectInfo y)
-            {
-                return x.Name.Equals(y.Name,StringComparison.InvariantCultureIgnoreCase);
-            }
-
-            public int GetHashCode(ObjectInfo obj)
-            {
-                return obj.Name.ToLower().GetHashCode();
-            }
-        }*/
-
-        public void SynchNow()
-        {             
-            if (_tcs!=null)
-                _tcs.TrySetResult(true);
-            else
-            {
-                //TODO: This may be OK for testing purposes, but we have no guarantee that it will
-                //work properly in production
-                PollRemoteFiles(repeat:false);
-            }
+            return _agent.GetEnumerable();
         }
 
-        //Remote files are polled periodically. Any changes are processed
-        public async Task PollRemoteFiles(DateTime? since = null,bool repeat=true)
+        public Task GetDeleteAwaiter()
         {
-            UpdateStatus(PithosStatus.Syncing);
-            StatusNotification.Notify(new PollNotification());
-
-            using (log4net.ThreadContext.Stacks["Retrieve Remote"].Push("All accounts"))
-            {
-                //If this poll fails, we will retry with the same since value
-                var nextSince = since;
-                try
-                {
-                    //Next time we will check for all changes since the current check minus 1 second
-                    //This is done to ensure there are no discrepancies due to clock differences
-                    DateTime current = DateTime.Now.AddSeconds(-1);
-
-                    var tasks = from accountInfo in _accounts
-                                select ProcessAccountFiles(accountInfo, since);
-
-                    await TaskEx.WhenAll(tasks.ToList());
-                                        
-                    _firstPoll = false;
-                    //Reschedule the poll with the current timestamp as a "since" value
-                    if (repeat)
-                        nextSince = current;
-                    else
-                        return;
-                }
-                catch (Exception ex)
-                {
-                    Log.ErrorFormat("Error while processing accounts\r\n{0}",ex);
-                    //In case of failure retry with the same "since" value
-                }
-                
-                UpdateStatus(PithosStatus.InSynch);
-                //Wait for the polling interval to pass or the Manual flat to be toggled
-                nextSince = await WaitForScheduledOrManualPoll(nextSince);
-
-                PollRemoteFiles(nextSince);
-
-            }
+            return _deleteAgent.PauseEvent.WaitAsync();
         }
-
-        private async Task<DateTime?> WaitForScheduledOrManualPoll(DateTime? since)
-        {            
-            _tcs = new TaskCompletionSource<bool>();
-            var wait = TaskEx.Delay(TimeSpan.FromSeconds(Settings.PollingInterval), _agent.CancellationToken);
-            var signaledTask = await TaskEx.WhenAny(_tcs.Task, wait);
-            //If polling is signalled by SynchNow, ignore the since tag
-            if (signaledTask is Task<bool>)
-                return null;
-            return since;
-        }
-
-        public async Task ProcessAccountFiles(AccountInfo accountInfo,DateTime? since=null)
-        {   
-            if (accountInfo==null)
-                throw new ArgumentNullException("accountInfo");
-            if (String.IsNullOrWhiteSpace(accountInfo.AccountPath))
-                throw new ArgumentException("The AccountInfo.AccountPath is empty","accountInfo");
-            Contract.EndContractBlock();
-
-
-            using (log4net.ThreadContext.Stacks["Retrieve Remote"].Push(accountInfo.UserName))
-            {
-                _pauseAgent.Wait();
-
-                Log.Info("Scheduled");
-                var client = new CloudFilesClient(accountInfo)
-                                 {
-                                     Proxy = PithosMonitor.ProxyFromSettings(this.Settings)
-                                 };
-
-                var containers = client.ListContainers(accountInfo.UserName);
-
-
-                CreateContainerFolders(accountInfo, containers);
-
-                try
-                {
-                    _pauseAgent.Wait();
-                    //Get the poll time now. We may miss some deletions but it's better to keep a file that was deleted
-                    //than delete a file that was created while we were executing the poll                    
-                    var pollTime = DateTime.Now;
-                    
-                    //Get the list of server objects changed since the last check
-                    //The name of the container is passed as state in order to create a dictionary of tasks in a subsequent step
-                    var listObjects = (from container in containers
-                                      select  Task<IList<ObjectInfo>>.Factory.StartNew(_ =>
-                                            client.ListObjects(accountInfo.UserName,container.Name, since),container.Name)).ToList();
-
-                    var listShared = Task<IList<ObjectInfo>>.Factory.StartNew(_ => client.ListSharedObjects(since), "shared");
-                    listObjects.Add(listShared);
-                    var listTasks = await Task.Factory.WhenAll(listObjects.ToArray());
-
-                    using (log4net.ThreadContext.Stacks["SCHEDULE"].Push("Process Results"))
-                    {
-                        var dict = listTasks.ToDictionary(t => t.AsyncState);
-
-                        //Get all non-trash objects. Remember, the container name is stored in AsyncState
-                        var remoteObjects = from objectList in listTasks
-                                            where (string) objectList.AsyncState != "trash"
-                                            from obj in objectList.Result
-                                            select obj;
-
-                        //TODO: Change the way deleted objects are detected.
-                        //The list operation returns all existing objects so we could detect deleted remote objects
-                        //by detecting objects that exist only locally. There are several cases where this is NOT the case:
-                        //1.    The first time the application runs, as there may be files that were added while 
-                        //      the application was down.
-                        //2.    An object that is currently being uploaded will not appear in the remote list
-                        //      until the upload finishes.
-                        //      SOLUTION 1: Check the upload/download queue for the file
-                        //      SOLUTION 2: Check the SQLite states for the file's entry. If it is being uploaded, 
-                        //          or its last modification was after the current poll, don't delete it. This way we don't
-                        //          delete objects whose upload finished too late to be included in the list.
-                        //We need to detect and protect against such situations
-                        //TODO: Does FileState have a LastModification field?
-                        //TODO: How do we update the LastModification field? Do we need to add SQLite triggers?
-                        //      Do we need to use a proper SQLite schema?
-                        //      We can create a trigger with 
-                        // CREATE TRIGGER IF NOT EXISTS update_last_modified UPDATE ON FileState FOR EACH ROW
-                        //  BEGIN
-                        //      UPDATE FileState SET LastModification=datetime('now')  WHERE Id=old.Id;
-                        //  END;
-                        //
-                        //NOTE: Some files may have been deleted remotely while the application was down. 
-                        //  We DO have to delete those files. Checking the trash makes it easy to detect them,
-                        //  Otherwise, we can't be really sure whether we need to upload or delete 
-                        //  the local-only files.
-                        //  SOLUTION 1: Ask the user when such a local-only file is detected during the first poll.
-                        //  SOLUTION 2: Mark conflict and ask the user as in #1
-
-                        var trashObjects = dict["trash"].Result;
-                        var sharedObjects = dict["shared"].Result;
-
-                        //Items with the same name, hash may be both in the container and the trash
-                        //Don't delete items that exist in the container
-                        var realTrash = from trash in trashObjects
-                                        where
-                                            !remoteObjects.Any(
-                                                info => info.Name == trash.Name && info.Hash == trash.Hash)
-                                        select trash;
-                        ProcessTrashedFiles(accountInfo, realTrash);
-
-
-                        var cleanRemotes = (from info in remoteObjects.Union(sharedObjects)
-                                     let name = info.Name
-                                     where !name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase) &&
-                                           !name.StartsWith(FolderConstants.CacheFolder + "/",
-                                                            StringComparison.InvariantCultureIgnoreCase)
-                                     select info).ToList();
-
-                        //TODO: Introduced state here, must remove somehow
-                        //Must move all this elsewhere
-                        SnapshotDifferencer differencer;
-                        if (!_differencers.TryGetValue(accountInfo.UserName,out differencer))
-                        {
-                            differencer = new SnapshotDifferencer();
-                            _differencers[accountInfo.UserName] = differencer;
-                        }
-                        differencer.Post(cleanRemotes);                        
-
-                        ProcessDeletedFiles(accountInfo, differencer.Deleted, pollTime);
-
-                        //Create a list of actions from the remote files
-                        var allActions = ChangesToActions(accountInfo, differencer.Changed)
-                                        .Union(
-                                        CreatesToActions(accountInfo,differencer.Created));
-
-                        
-                        //var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
-
-                        //And remove those that are already being processed by the agent
-                        var distinctActions = allActions
-                            .Except(_agent.GetEnumerable(), new PithosMonitor.LocalFileComparer())
-                            .ToList();
-
-                        //Queue all the actions
-                        foreach (var message in distinctActions)
-                        {
-                            Post(message);
-                        }
-
-                        Log.Info("[LISTENER] End Processing");
-                    }
-                }
-                catch (Exception ex)
-                {
-                    Log.ErrorFormat("[FAIL] ListObjects for{0} in ProcessRemoteFiles with {1}", accountInfo.UserName, ex);
-                    return;
-                }
-
-                Log.Info("[LISTENER] Finished");
-
-            }
-        }
-
-
-        Dictionary<string, SnapshotDifferencer> _differencers= new Dictionary<string, SnapshotDifferencer>();
-
-/*
-        Dictionary<string, List<ObjectInfo>> _currentSnapshot = new Dictionary<string, List<ObjectInfo>>();
-        Dictionary<string, List<ObjectInfo>> _previousSnapshot = new Dictionary<string, List<ObjectInfo>>();
-*/
-
-        /// <summary>
-        /// Deletes local files that are not found in the list of cloud files
-        /// </summary>
-        /// <param name="accountInfo"></param>
-        /// <param name="cloudFiles"></param>
-        /// <param name="pollTime"></param>
-        private void ProcessDeletedFiles(AccountInfo accountInfo, IEnumerable<ObjectInfo> cloudFiles, DateTime pollTime)
-        {
-            if (accountInfo == null)
-                throw new ArgumentNullException("accountInfo");
-            if (String.IsNullOrWhiteSpace(accountInfo.AccountPath))
-                throw new ArgumentException("The AccountInfo.AccountPath is empty", "accountInfo");
-            if (cloudFiles == null)
-                throw new ArgumentNullException("cloudFiles");
-            Contract.EndContractBlock();
-
-            //On the first run
-            if (_firstPoll)
-            {
-                //Only consider files that are not being modified, ie they are in the Unchanged state            
-                var deleteCandidates = FileState.Queryable.Where(state =>
-                    state.FilePath.StartsWith(accountInfo.AccountPath)
-                    && state.FileStatus == FileStatus.Unchanged).ToList();
-
-
-                //TODO: filesToDelete must take into account the Others container            
-                var filesToDelete = (from deleteCandidate in deleteCandidates
-                                         let localFile = FileInfoExtensions.FromPath(deleteCandidate.FilePath)
-                                         let relativeFilePath = localFile.AsRelativeTo(accountInfo.AccountPath)
-                                     where
-                                         !cloudFiles.Any(r => r.RelativeUrlToFilePath(accountInfo.UserName) == relativeFilePath)
-                                     select localFile).ToList();
-            
-
-
-                //Set the status of missing files to Conflict
-                foreach (var item in filesToDelete)
-                {
-                    //Try to acquire a gate on the file, to take into account files that have been dequeued
-                    //and are being processed
-                    using (var gate = NetworkGate.Acquire(item.FullName, NetworkOperation.Deleting))
-                    {
-                        if (gate.Failed)
-                            continue;
-                        StatusKeeper.SetFileState(item.FullName, FileStatus.Conflict, FileOverlayStatus.Deleted);
-                    }
-                }
-                UpdateStatus(PithosStatus.HasConflicts);
-                StatusNotification.NotifyConflicts(filesToDelete, String.Format("{0} local files are missing from Pithos, possibly because they were deleted",filesToDelete.Count));
-                StatusNotification.NotifyForFiles(filesToDelete, String.Format("{0} files were deleted", filesToDelete.Count), TraceLevel.Info);
-            }
-            else
-            {
-                var deletedFiles = new List<FileSystemInfo>();
-                foreach (var objectInfo in cloudFiles)
-                {
-                    var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
-                    var item = GetFileAgent(accountInfo).GetFileSystemInfo(relativePath);
-                    if (item.Exists)
-                    {
-                        //Try to acquire a gate on the file, to take into account files that have been dequeued
-                        //and are being processed
-                        //TODO: The gate is not enough. Perhaps we need to keep a journal of processed files and check against
-                        //that as well.
-/*
-                        using (var gate = NetworkGate.Acquire(item.FullName, NetworkOperation.Deleting))
-                        {
-                            if (gate.Failed)
-                                continue;
-*/
-                            if ((item.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
-                            {
-                                item.Attributes = item.Attributes & ~FileAttributes.ReadOnly;
-
-                            }
-                            item.Delete();
-                            DateTime lastDate;
-                            _lastSeen.TryRemove(item.FullName, out lastDate);
-                            deletedFiles.Add(item);
-/*
-                        }
-*/
-                    }
-                    StatusKeeper.SetFileState(item.FullName, FileStatus.Deleted, FileOverlayStatus.Deleted);                    
-                }
-                StatusNotification.NotifyForFiles(deletedFiles, String.Format("{0} files were deleted", deletedFiles.Count), TraceLevel.Info);
-            }
-
-        }
-
-        private static void CreateContainerFolders(AccountInfo accountInfo, IEnumerable<ContainerInfo> containers)
+        public CancellationToken CancellationToken
         {
-            var containerPaths = from container in containers
-                                 let containerPath = Path.Combine(accountInfo.AccountPath, container.Name)
-                                 where container.Name != FolderConstants.TrashContainer && !Directory.Exists(containerPath)
-                                 select containerPath;
-
-            foreach (var path in containerPaths)
-            {
-                Directory.CreateDirectory(path);
-            }
-        }
-
-        //Creates an appropriate action for each server file
-        private IEnumerable<CloudAction> ChangesToActions(AccountInfo accountInfo,IEnumerable<ObjectInfo> changes)
-        {
-            if (changes==null)
-                throw new ArgumentNullException();
-            Contract.EndContractBlock();
-            var fileAgent = GetFileAgent(accountInfo);
-
-            //In order to avoid multiple iterations over the files, we iterate only once
-            //over the remote files
-            foreach (var objectInfo in changes)
-            {
-                var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
-                //and remove any matching objects from the list, adding them to the commonObjects list
-                if (fileAgent.Exists(relativePath))
-                {
-                    //If a directory object already exists, we don't need to perform any other action                    
-                    var localFile = fileAgent.GetFileSystemInfo(relativePath);
-                    if (objectInfo.Content_Type == @"application/directory" && localFile is DirectoryInfo)
-                        continue;
-                    using (new SessionScope(FlushAction.Never))
-                    {
-                        var state =  StatusKeeper.GetStateByFilePath(localFile.FullName);
-                        _lastSeen[localFile.FullName] = DateTime.Now;
-                        //FileState.FindByFilePath(localFile.FullName);
-                        //Common files should be checked on a per-case basis to detect differences, which is newer
-
-                        yield return new CloudAction(accountInfo, CloudActionType.MustSynch,
-                                                     localFile, objectInfo, state, accountInfo.BlockSize,
-                                                     accountInfo.BlockHash);
-                    }
-                }
-                else
-                {
-                    //Remote files should be downloaded
-                    yield return new CloudDownloadAction(accountInfo,objectInfo);
-                }
-            }            
+            get { return _agent.CancellationToken; }
         }
 
-        private IEnumerable<CloudAction> CreatesToActions(AccountInfo accountInfo,IEnumerable<ObjectInfo> creates)
-        {
-            if (creates==null)
-                throw new ArgumentNullException();
-            Contract.EndContractBlock();
-            var fileAgent = GetFileAgent(accountInfo);
-
-            //In order to avoid multiple iterations over the files, we iterate only once
-            //over the remote files
-            foreach (var objectInfo in creates)
-            {
-                var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
-                //and remove any matching objects from the list, adding them to the commonObjects list
-                if (fileAgent.Exists(relativePath))
-                {
-                    //If the object already exists, we probably have a conflict
-                    //If a directory object already exists, we don't need to perform any other action                    
-                    var localFile = fileAgent.GetFileSystemInfo(relativePath);
-                    StatusKeeper.SetFileState(localFile.FullName,FileStatus.Conflict,FileOverlayStatus.Conflict);
-                }
-                else
-                {
-                    //Remote files should be downloaded
-                    yield return new CloudDownloadAction(accountInfo,objectInfo);
-                }
-            }            
-        }
-
-        //Creates an appropriate action for each server file
-/*
-        private IEnumerable<CloudAction> ObjectsToActions(AccountInfo accountInfo,IEnumerable<ObjectInfo> remote)
-        {
-            if (remote==null)
-                throw new ArgumentNullException();
-            Contract.EndContractBlock();
-            var fileAgent = GetFileAgent(accountInfo);
-
-            //In order to avoid multiple iterations over the files, we iterate only once
-            //over the remote files
-            foreach (var objectInfo in remote)
-            {
-                var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
-                //and remove any matching objects from the list, adding them to the commonObjects list
-                
-                if (fileAgent.Exists(relativePath))
-                {
-                    //If a directory object already exists, we don't need to perform any other action                    
-                    var localFile = fileAgent.GetFileSystemInfo(relativePath);
-                    if (objectInfo.Content_Type == @"application/directory" && localFile is DirectoryInfo)
-                        continue;
-                    using (new SessionScope(FlushAction.Never))
-                    {
-                        var state =  StatusKeeper.GetStateByFilePath(localFile.FullName);
-                        _lastSeen[localFile.FullName] = DateTime.Now;
-                        //FileState.FindByFilePath(localFile.FullName);
-                        //Common files should be checked on a per-case basis to detect differences, which is newer
-
-                        yield return new CloudAction(accountInfo, CloudActionType.MustSynch,
-                                                     localFile, objectInfo, state, accountInfo.BlockSize,
-                                                     accountInfo.BlockHash);
-                    }
-                }
-                else
-                {
-                    //If there is no match we add them to the localFiles list
-                    //but only if the file is not marked for deletion
-                    var targetFile = Path.Combine(accountInfo.AccountPath, relativePath);
-                    var fileStatus = StatusKeeper.GetFileStatus(targetFile);
-                    if (fileStatus != FileStatus.Deleted)
-                    {
-                        //Remote files should be downloaded
-                        yield return new CloudDownloadAction(accountInfo,objectInfo);
-                    }
-                }
-            }            
-        }
-*/
-
         private static FileAgent GetFileAgent(AccountInfo accountInfo)
         {
             return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);
         }
 
-        private void ProcessTrashedFiles(AccountInfo accountInfo,IEnumerable<ObjectInfo> trashObjects)
-        {
-            var fileAgent = GetFileAgent(accountInfo);
-            foreach (var trashObject in trashObjects)
-            {
-                var barePath = trashObject.RelativeUrlToFilePath(accountInfo.UserName);
-                //HACK: Assume only the "pithos" container is used. Must find out what happens when
-                //deleting a file from a different container
-                var relativePath = Path.Combine("pithos", barePath);
-                fileAgent.Delete(relativePath);                                
-            }
-        }
 
 
         private void RenameCloudFile(AccountInfo accountInfo,CloudMoveAction action)
@@ -950,37 +413,6 @@ namespace Pithos.Core.Agents
             NativeMethods.RaiseChangeNotification(newFilePath);
         }
 
-        private 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 ( log4net.ThreadContext.Stacks["DeleteCloudFile"].Push("Delete"))
-            {
-                var fileName= cloudFile.RelativeUrlToFilePath(accountInfo.UserName);
-                var info = fileAgent.GetFileSystemInfo(fileName);                
-                var fullPath = info.FullName.ToLower();
-
-                StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified);
-
-                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);
-            }
-        }
-
         //Download a file.
         private async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile , string filePath)
         {
@@ -1012,8 +444,6 @@ namespace Pithos.Core.Agents
             {
                 if (gate.Failed)
                     return;
-                //The file's hashmap will be stored in the same location with the extension .hashmap
-                //var hashPath = Path.Combine(FileAgent.CachePath, relativePath + ".hashmap");
                 
                 var client = new CloudFilesClient(accountInfo);
                 var account = cloudFile.Account;
@@ -1309,24 +739,7 @@ namespace Pithos.Core.Agents
 
         }
 
-        //Returns true if an action concerns a file that was deleted
-        private 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;
-        }
+
 
         private bool HandleUploadWebException(CloudAction action, WebException exc)
         {