Changed AsyncManualResetEvent.Set behaviour to occur asynchronously. In the previous...
[pithos-ms-client] / trunk / Pithos.Core / Agents / NetworkAgent.cs
index 9b2f301..93bbcfc 100644 (file)
@@ -1,6 +1,6 @@
 // -----------------------------------------------------------------------
 // <copyright file="NetworkAgent.cs" company="GRNET">
-// Copyright 2011 GRNET S.A. All rights reserved.
+// 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
@@ -35,6 +35,9 @@
 // </copyright>
 // -----------------------------------------------------------------------
 
+//TODO: Now there is a UUID tag. This can be used for renames/moves
+
+
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -60,12 +63,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; }
@@ -80,7 +79,11 @@ namespace Pithos.Core.Agents
         public IPithosSettings Settings { get; set; }
 
         private bool _firstPoll = true;
-        private TaskCompletionSource<bool> _tcs;
+        
+        //The Sync Event signals a manual synchronisation
+        private readonly AsyncManualResetEvent _syncEvent=new AsyncManualResetEvent();
+
+        private ConcurrentDictionary<string, DateTime> _lastSeen = new ConcurrentDictionary<string, DateTime>();
 
         public void Start()
         {
@@ -90,7 +93,7 @@ namespace Pithos.Core.Agents
                 Action loop = null;
                 loop = () =>
                 {
-                    _pauseAgent.Wait();
+                    _deleteAgent.PauseEvent.Wait();
                     var message = inbox.Receive();
                     var process=message.Then(Process,inbox.CancellationToken);
                     inbox.LoopAsync(process, loop);
@@ -98,18 +101,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)
@@ -120,6 +111,7 @@ 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"))
@@ -130,14 +122,13 @@ namespace Pithos.Core.Agents
                 var downloadPath = action.GetDownloadPath();
 
                 try
-                {
-
+                {                    
                     if (action.Action == CloudActionType.DeleteCloud)
-                    {
+                    {                        
                         //Redirect deletes to the delete agent 
-                        _deleteAgent.Post((CloudDeleteAction)action);                        
+                        _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)
@@ -155,7 +146,7 @@ namespace Pithos.Core.Agents
                                 await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
                                 break;
                             case CloudActionType.RenameCloud:
-                                var moveAction = (CloudMoveAction) action;
+                                var moveAction = (CloudMoveAction)action;
                                 RenameCloudFile(accountInfo, moveAction);
                                 break;
                             case CloudActionType.MustSynch:
@@ -186,7 +177,7 @@ namespace Pithos.Core.Agents
                     Log.ErrorFormat("{0} : {1} -> {2}  failed because the directory was not found.\n Rescheduling a delete",
                         action.Action, action.LocalFile, action.CloudFile);
                     //Post a delete action for the missing file
-                    Post(new CloudDeleteAction(action));                    
+                    Post(new CloudDeleteAction(action));
                 }
                 catch (FileNotFoundException)
                 {
@@ -201,101 +192,21 @@ namespace Pithos.Core.Agents
                                      action.Action, action.LocalFile, action.CloudFile, exc);
 
                     _agent.Post(action);
-                }                
-            }
-        }
-
-        /// <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
                 {
-                    if (_deleteAgent.InputCount == 0)
-                        _pauseAgent.Set();
-
+                    UpdateStatus(PithosStatus.InSynch);                    
                 }
             }
         }
 
-        private static string GetFileKey(ObjectInfo info)
+        private void UpdateStatus(PithosStatus status)
         {
-            var key = String.Format("{0}/{1}/{2}", info.Account, info.Container,info.Name);
-            return key;
+            StatusKeeper.SetPithosStatus(status);
+            StatusNotification.Notify(new Notification());
         }
 
+        
         private async Task SyncFiles(AccountInfo accountInfo,CloudAction action)
         {
             if (accountInfo == null)
@@ -376,6 +287,7 @@ namespace Pithos.Core.Agents
             Contract.EndContractBlock();
 
             StatusKeeper.SetFileOverlayStatus(downloadPath, FileOverlayStatus.Conflict);
+            UpdateStatus(PithosStatus.HasConflicts);
             var message = String.Format("Conflict detected for file {0}", downloadPath);
             Log.Warn(message);
             StatusNotification.NotifyChange(message, TraceLevel.Warning);
@@ -389,7 +301,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)
@@ -400,7 +312,7 @@ namespace Pithos.Core.Agents
                     cloudAction.TopHash =
                         new Lazy<string>(() => Signature.CalculateTreeHashAsync(localFile,
                                                                                 accountInfo.BlockSize,
-                                                                                accountInfo.BlockHash).Result
+                                                                                accountInfo.BlockHash, Settings.HashingParallelism).Result
                                                     .TopHash.ToHashString());
                 else
                 {
@@ -418,72 +330,75 @@ namespace Pithos.Core.Agents
             else
                 _agent.Post(cloudAction);
         }
+       
 
-       /* class ObjectInfoByNameComparer:IEqualityComparer<ObjectInfo>
-        {
-            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();
-            }
-        }*/
-
+        /// <summary>
+        /// Start a manual synchronization
+        /// </summary>
         public void SynchNow()
-        {             
-            if (_tcs!=null)
-                _tcs.SetResult(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);
-            }
+        {       
+            _syncEvent.Set();
         }
 
         //Remote files are polled periodically. Any changes are processed
-        public async Task PollRemoteFiles(DateTime? since = null,bool repeat=true)
+        public async Task PollRemoteFiles(DateTime? since = null)
         {
+            Debug.Assert(Thread.CurrentThread.IsBackground,"Polling Ended up in the main thread!");
 
-            _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>)
-                since = null;
+            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 nextSince = DateTime.Now.AddSeconds(-1);
+                    var current = DateTime.Now.AddSeconds(-1);
 
                     var tasks = from accountInfo in _accounts
                                 select ProcessAccountFiles(accountInfo, since);
 
                     await TaskEx.WhenAll(tasks.ToList());
-
+                                        
                     _firstPoll = false;
-                    if (repeat)
-                        PollRemoteFiles(nextSince);
+                    //Reschedule the poll with the current timestamp as a "since" value
+                    nextSince = current;
                 }
                 catch (Exception ex)
                 {
                     Log.ErrorFormat("Error while processing accounts\r\n{0}",ex);
-                    //In case of failure retry with the same parameter
-                    PollRemoteFiles(since);
+                    //In case of failure retry with the same "since" value
                 }
                 
+                UpdateStatus(PithosStatus.InSynch);
+                //Wait for the polling interval to pass or the Sync event to be signalled
+                nextSince = await WaitForScheduledOrManualPoll(nextSince);
+
+                PollRemoteFiles(nextSince);
 
             }
         }
 
+        /// <summary>
+        /// Wait for the polling period to expire or a manual sync request
+        /// </summary>
+        /// <param name="since"></param>
+        /// <returns></returns>
+        private async Task<DateTime?> WaitForScheduledOrManualPoll(DateTime? since)
+        {
+            var sync=_syncEvent.WaitAsync();
+            var wait = TaskEx.Delay(TimeSpan.FromSeconds(Settings.PollingInterval), _agent.CancellationToken);
+            var signaledTask = await TaskEx.WhenAny(sync, 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)
@@ -492,29 +407,34 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("The AccountInfo.AccountPath is empty","accountInfo");
             Contract.EndContractBlock();
 
+
             using (log4net.ThreadContext.Stacks["Retrieve Remote"].Push(accountInfo.UserName))
             {
+                await _deleteAgent.PauseEvent.WaitAsync();
+
                 Log.Info("Scheduled");
-                var client=new CloudFilesClient(accountInfo);
+                var client = new CloudFilesClient(accountInfo);
 
                 var containers = client.ListContainers(accountInfo.UserName);
-                
+
+
                 CreateContainerFolders(accountInfo, containers);
 
                 try
                 {
-                    _pauseAgent.Wait();
+                    await _deleteAgent.PauseEvent.WaitAsync();
                     //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
+                    //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
+                    var listObjects = (from container in containers
                                       select  Task<IList<ObjectInfo>>.Factory.StartNew(_ =>
-                                            client.ListObjects(accountInfo.UserName,container.Name, since),container.Name);
-
+                                            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"))
@@ -527,36 +447,8 @@ namespace Pithos.Core.Agents
                                             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 = ((Task<IList<ObjectInfo>>) task.Result[2]).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
@@ -567,25 +459,22 @@ namespace Pithos.Core.Agents
                                         select trash;
                         ProcessTrashedFiles(accountInfo, realTrash);
 
-
-                        var cleanRemotes = (from info in remoteObjects
-                                     //.Union(sharedObjects)
+                        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();
 
-
-
-                        ProcessDeletedFiles(accountInfo, cleanRemotes, pollTime);
+                        var differencer = _differencer.PostSnapshot(accountInfo, cleanRemotes);
+                        
+                        ProcessDeletedFiles(accountInfo, differencer.Deleted, pollTime);
 
                         //Create a list of actions from the remote files
-                        var allActions = ObjectsToActions(accountInfo, cleanRemotes);
-
+                        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())
@@ -611,6 +500,8 @@ namespace Pithos.Core.Agents
             }
         }
 
+        AccountsDifferencer _differencer= new AccountsDifferencer();
+
         /// <summary>
         /// Deletes local files that are not found in the list of cloud files
         /// </summary>
@@ -627,22 +518,65 @@ namespace Pithos.Core.Agents
                 throw new ArgumentNullException("cloudFiles");
             Contract.EndContractBlock();
 
-            if (_firstPoll) return;
+            //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();
+            
 
-            var deleteCandidates = from state in FileState.Queryable
-                                   where state.Modified <= pollTime && state.FilePath.StartsWith(accountInfo.AccountPath)
-                                   select state;
 
-            foreach (var deleteCandidate in deleteCandidates)
+                //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 localFile = FileInfoExtensions.FromPath(deleteCandidate.FilePath);
-                var relativeFilePath=localFile.AsRelativeTo(accountInfo.AccountPath);
-                if (!cloudFiles.Any(r => Path.Combine(r.Container, r.Name) == relativeFilePath))
+                var deletedFiles = new List<FileSystemInfo>();
+                foreach (var objectInfo in cloudFiles)
                 {
-                    localFile.Delete();
-                    StatusKeeper.ClearFileStatus(deleteCandidate.FilePath);
+                    var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
+                    var item = GetFileAgent(accountInfo).GetFileSystemInfo(relativePath);
+                    if (item.Exists)
+                    {
+                        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)
@@ -659,20 +593,19 @@ namespace Pithos.Core.Agents
         }
 
         //Creates an appropriate action for each server file
-        private IEnumerable<CloudAction> ObjectsToActions(AccountInfo accountInfo,IEnumerable<ObjectInfo> remote)
+        private IEnumerable<CloudAction> ChangesToActions(AccountInfo accountInfo,IEnumerable<ObjectInfo> changes)
         {
-            if (remote==null)
+            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 remote)
+            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                    
@@ -682,7 +615,7 @@ namespace Pithos.Core.Agents
                     using (new SessionScope(FlushAction.Never))
                     {
                         var state =  StatusKeeper.GetStateByFilePath(localFile.FullName);
-                        //FileState.FindByFilePath(localFile.FullName);
+                        _lastSeen[localFile.FullName] = DateTime.Now;
                         //Common files should be checked on a per-case basis to detect differences, which is newer
 
                         yield return new CloudAction(accountInfo, CloudActionType.MustSynch,
@@ -692,19 +625,41 @@ namespace Pithos.Core.Agents
                 }
                 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);
-                    }
+                    //Remote files should be downloaded
+                    yield return new CloudDownloadAction(accountInfo,objectInfo);
                 }
             }            
         }
 
+        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);
+                }
+            }            
+        }
+
+
         private static FileAgent GetFileAgent(AccountInfo accountInfo)
         {
             return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);
@@ -771,37 +726,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)
         {
@@ -818,6 +742,7 @@ namespace Pithos.Core.Agents
             if (!Path.IsPathRooted(filePath))
                 throw new ArgumentException("The filePath must be rooted", "filePath");
             Contract.EndContractBlock();
+            
 
             var localPath = Interfaces.FileInfoExtensions.GetProperFilePathCapitalization(filePath);
             var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
@@ -832,8 +757,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;
@@ -845,7 +768,7 @@ namespace Pithos.Core.Agents
                         Directory.CreateDirectory(localPath);
                 }
                 else
-                {
+                {                    
                     //Retrieve the hashmap from the server
                     var serverHash = await client.GetHashMap(account, container, url);
                     //If it's a small file
@@ -900,6 +823,7 @@ namespace Pithos.Core.Agents
                 if (localHash==cloudHash)
                     return;
             }
+            StatusNotification.Notify(new CloudNotification { Data = cloudFile });
 
             var fileAgent = GetFileAgent(accountInfo);
             //Calculate the relative file path for the new file
@@ -956,7 +880,7 @@ namespace Pithos.Core.Agents
             
                         
             //Calculate the file's treehash
-            var treeHash = await Signature.CalculateTreeHashAsync(localPath, serverHash.BlockSize, serverHash.BlockHash);
+            var treeHash = await Signature.CalculateTreeHashAsync(localPath, serverHash.BlockSize, serverHash.BlockHash, 2);
                 
             //And compare it with the server's hash
             var upHashes = serverHash.GetHashesAsStrings();
@@ -967,6 +891,8 @@ namespace Pithos.Core.Agents
                 var upHash = upHashes[i];
                 if (!localHashes.ContainsKey(upHash))
                 {
+                    StatusNotification.Notify(new CloudNotification { Data = cloudFile });
+
                     if (blockUpdater.UseOrphan(i, upHash))
                     {
                         Log.InfoFormat("[BLOCK GET] ORPHAN FOUND for {0} of {1} for {2}", i, upHashes.Length, localPath);
@@ -1009,7 +935,7 @@ namespace Pithos.Core.Agents
             Contract.EndContractBlock();
 
             try
-            {
+            {                
                 var accountInfo = action.AccountInfo;
 
                 var fileInfo = action.LocalFile;
@@ -1092,7 +1018,7 @@ namespace Pithos.Core.Agents
 
                         //First, calculate the tree hash
                         var treeHash = await Signature.CalculateTreeHashAsync(fullFileName, accountInfo.BlockSize,
-                                                                              accountInfo.BlockHash);
+                                                                              accountInfo.BlockHash, 2);
 
                         await UploadWithHashMap(accountInfo, cloudFile, fileInfo as FileInfo, cloudFile.Name, treeHash);
                     }
@@ -1126,20 +1052,7 @@ namespace Pithos.Core.Agents
 
         }
 
-        private bool IsDeletedFile(CloudAction action)
-        {            
-            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)
         {