Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / NetworkAgent.cs @ 303596f9

History | View | Annotate | Download (65.2 kB)

1 f3d080df Panagiotis Kanavos
// -----------------------------------------------------------------------
2 f3d080df Panagiotis Kanavos
// <copyright file="NetworkAgent.cs" company="GRNET">
3 1cf5689c Panagiotis Kanavos
// Copyright 2011-2012 GRNET S.A. All rights reserved.
4 f3d080df Panagiotis Kanavos
// 
5 f3d080df Panagiotis Kanavos
// Redistribution and use in source and binary forms, with or
6 f3d080df Panagiotis Kanavos
// without modification, are permitted provided that the following
7 f3d080df Panagiotis Kanavos
// conditions are met:
8 f3d080df Panagiotis Kanavos
// 
9 f3d080df Panagiotis Kanavos
//   1. Redistributions of source code must retain the above
10 f3d080df Panagiotis Kanavos
//      copyright notice, this list of conditions and the following
11 f3d080df Panagiotis Kanavos
//      disclaimer.
12 f3d080df Panagiotis Kanavos
// 
13 f3d080df Panagiotis Kanavos
//   2. Redistributions in binary form must reproduce the above
14 f3d080df Panagiotis Kanavos
//      copyright notice, this list of conditions and the following
15 f3d080df Panagiotis Kanavos
//      disclaimer in the documentation and/or other materials
16 f3d080df Panagiotis Kanavos
//      provided with the distribution.
17 f3d080df Panagiotis Kanavos
// 
18 f3d080df Panagiotis Kanavos
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 f3d080df Panagiotis Kanavos
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 f3d080df Panagiotis Kanavos
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 f3d080df Panagiotis Kanavos
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 f3d080df Panagiotis Kanavos
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 f3d080df Panagiotis Kanavos
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 f3d080df Panagiotis Kanavos
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 f3d080df Panagiotis Kanavos
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 f3d080df Panagiotis Kanavos
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 f3d080df Panagiotis Kanavos
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 f3d080df Panagiotis Kanavos
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 f3d080df Panagiotis Kanavos
// POSSIBILITY OF SUCH DAMAGE.
30 f3d080df Panagiotis Kanavos
// 
31 f3d080df Panagiotis Kanavos
// The views and conclusions contained in the software and
32 f3d080df Panagiotis Kanavos
// documentation are those of the authors and should not be
33 f3d080df Panagiotis Kanavos
// interpreted as representing official policies, either expressed
34 f3d080df Panagiotis Kanavos
// or implied, of GRNET S.A.
35 f3d080df Panagiotis Kanavos
// </copyright>
36 f3d080df Panagiotis Kanavos
// -----------------------------------------------------------------------
37 f3d080df Panagiotis Kanavos
38 f3d080df Panagiotis Kanavos
using System;
39 4f6d51d4 Panagiotis Kanavos
using System.Collections.Concurrent;
40 9c4346c9 Panagiotis Kanavos
using System.Collections.Generic;
41 9c4346c9 Panagiotis Kanavos
using System.ComponentModel.Composition;
42 9c4346c9 Panagiotis Kanavos
using System.Diagnostics;
43 9c4346c9 Panagiotis Kanavos
using System.Diagnostics.Contracts;
44 9c4346c9 Panagiotis Kanavos
using System.IO;
45 9c4346c9 Panagiotis Kanavos
using System.Linq;
46 0af3141d Panagiotis Kanavos
using System.Net;
47 0b346191 Panagiotis Kanavos
using System.Threading;
48 9c4346c9 Panagiotis Kanavos
using System.Threading.Tasks;
49 3c76f045 Panagiotis Kanavos
using System.Threading.Tasks.Dataflow;
50 e81dd1f6 Panagiotis Kanavos
using Castle.ActiveRecord;
51 9c4346c9 Panagiotis Kanavos
using Pithos.Interfaces;
52 5ce54458 Panagiotis Kanavos
using Pithos.Network;
53 cfed7823 Panagiotis Kanavos
using log4net;
54 9c4346c9 Panagiotis Kanavos
55 9c4346c9 Panagiotis Kanavos
namespace Pithos.Core.Agents
56 9c4346c9 Panagiotis Kanavos
{
57 f3d080df Panagiotis Kanavos
    //TODO: Ensure all network operations use exact casing. Pithos is case sensitive
58 9c4346c9 Panagiotis Kanavos
    [Export]
59 9c4346c9 Panagiotis Kanavos
    public class NetworkAgent
60 9c4346c9 Panagiotis Kanavos
    {
61 9c4346c9 Panagiotis Kanavos
        private Agent<CloudAction> _agent;
62 9c4346c9 Panagiotis Kanavos
63 f3d080df Panagiotis Kanavos
        //A separate agent is used to execute delete actions immediatelly;
64 3c76f045 Panagiotis Kanavos
        private ActionBlock<CloudDeleteAction> _deleteAgent;
65 14ecd267 Panagiotis Kanavos
        readonly ConcurrentDictionary<string,DateTime> _deletedFiles=new ConcurrentDictionary<string, DateTime>();
66 5e31048f Panagiotis Kanavos
67 0b346191 Panagiotis Kanavos
68 0b346191 Panagiotis Kanavos
69 e81dd1f6 Panagiotis Kanavos
        [System.ComponentModel.Composition.Import]
70 9c4346c9 Panagiotis Kanavos
        public IStatusKeeper StatusKeeper { get; set; }
71 9c4346c9 Panagiotis Kanavos
        
72 9c4346c9 Panagiotis Kanavos
        public IStatusNotification StatusNotification { get; set; }
73 9c4346c9 Panagiotis Kanavos
74 5120f3cb Panagiotis Kanavos
        private static readonly ILog Log = LogManager.GetLogger("NetworkAgent");
75 cfed7823 Panagiotis Kanavos
76 4f6d51d4 Panagiotis Kanavos
        private readonly ConcurrentBag<AccountInfo> _accounts = new ConcurrentBag<AccountInfo>();
77 5ce54458 Panagiotis Kanavos
78 133f83c2 Panagiotis Kanavos
        [System.ComponentModel.Composition.Import]
79 133f83c2 Panagiotis Kanavos
        public IPithosSettings Settings { get; set; }
80 540b8cf8 Panagiotis Kanavos
81 540b8cf8 Panagiotis Kanavos
        private bool _firstPoll = true;
82 303596f9 Panagiotis Kanavos
        
83 29a6b387 Panagiotis Kanavos
        private TaskCompletionSource<bool> _tcs;
84 303596f9 Panagiotis Kanavos
        private readonly AsyncManualResetEvent _pauseAgent = new AsyncManualResetEvent(true);
85 303596f9 Panagiotis Kanavos
86 303596f9 Panagiotis Kanavos
        private ConcurrentDictionary<string, DateTime> _lastSeen = new ConcurrentDictionary<string, DateTime>();
87 29a6b387 Panagiotis Kanavos
88 73cdd135 Panagiotis Kanavos
        public void Start()
89 9c4346c9 Panagiotis Kanavos
        {
90 540b8cf8 Panagiotis Kanavos
            _firstPoll = true;
91 9c4346c9 Panagiotis Kanavos
            _agent = Agent<CloudAction>.Start(inbox =>
92 9c4346c9 Panagiotis Kanavos
            {
93 9c4346c9 Panagiotis Kanavos
                Action loop = null;
94 9c4346c9 Panagiotis Kanavos
                loop = () =>
95 9c4346c9 Panagiotis Kanavos
                {
96 c99473ae Panagiotis Kanavos
                    _pauseAgent.Wait();
97 9c4346c9 Panagiotis Kanavos
                    var message = inbox.Receive();
98 a64c87c8 Panagiotis Kanavos
                    var process=message.Then(Process,inbox.CancellationToken);
99 a27aa447 Panagiotis Kanavos
                    inbox.LoopAsync(process, loop);
100 a64c87c8 Panagiotis Kanavos
                };
101 a64c87c8 Panagiotis Kanavos
                loop();
102 f3d080df Panagiotis Kanavos
            });
103 f3d080df Panagiotis Kanavos
104 3c76f045 Panagiotis Kanavos
            _deleteAgent = new ActionBlock<CloudDeleteAction>(message =>ProcessDelete(message),new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=4});
105 3c76f045 Panagiotis Kanavos
            /*
106 f3d080df Panagiotis Kanavos
                Action loop = null;
107 f3d080df Panagiotis Kanavos
                loop = () =>
108 f3d080df Panagiotis Kanavos
                            {
109 f3d080df Panagiotis Kanavos
                                var message = inbox.Receive();
110 f3d080df Panagiotis Kanavos
                                var process = message.Then(ProcessDelete,inbox.CancellationToken);
111 f3d080df Panagiotis Kanavos
                                inbox.LoopAsync(process, loop);
112 f3d080df Panagiotis Kanavos
                            };
113 f3d080df Panagiotis Kanavos
                loop();
114 3c76f045 Panagiotis Kanavos
*/
115 f3d080df Panagiotis Kanavos
116 a64c87c8 Panagiotis Kanavos
        }
117 a27aa447 Panagiotis Kanavos
118 27361404 Panagiotis Kanavos
        private async Task Process(CloudAction action)
119 a64c87c8 Panagiotis Kanavos
        {
120 a64c87c8 Panagiotis Kanavos
            if (action == null)
121 a64c87c8 Panagiotis Kanavos
                throw new ArgumentNullException("action");
122 c53aa229 Panagiotis Kanavos
            if (action.AccountInfo==null)
123 c53aa229 Panagiotis Kanavos
                throw new ArgumentException("The action.AccountInfo is empty","action");
124 a64c87c8 Panagiotis Kanavos
            Contract.EndContractBlock();
125 a27aa447 Panagiotis Kanavos
126 eae84ae8 Panagiotis Kanavos
            UpdateStatus(PithosStatus.Syncing);
127 c53aa229 Panagiotis Kanavos
            var accountInfo = action.AccountInfo;
128 c53aa229 Panagiotis Kanavos
129 5120f3cb Panagiotis Kanavos
            using (log4net.ThreadContext.Stacks["NETWORK"].Push("PROCESS"))
130 cfed7823 Panagiotis Kanavos
            {                
131 27361404 Panagiotis Kanavos
                Log.InfoFormat("[ACTION] Start Processing {0}", action);
132 cfed7823 Panagiotis Kanavos
133 1bfc38f1 Panagiotis Kanavos
                var cloudFile = action.CloudFile;
134 1bfc38f1 Panagiotis Kanavos
                var downloadPath = action.GetDownloadPath();
135 cfed7823 Panagiotis Kanavos
136 cfed7823 Panagiotis Kanavos
                try
137 eae84ae8 Panagiotis Kanavos
                {                    
138 14ecd267 Panagiotis Kanavos
                    if (action.Action == CloudActionType.DeleteCloud)
139 eae84ae8 Panagiotis Kanavos
                    {                        
140 14ecd267 Panagiotis Kanavos
                        //Redirect deletes to the delete agent 
141 e5b65606 Panagiotis Kanavos
                        _deleteAgent.Post((CloudDeleteAction)action);
142 14ecd267 Panagiotis Kanavos
                    }
143 14ecd267 Panagiotis Kanavos
                    if (IsDeletedFile(action))
144 14ecd267 Panagiotis Kanavos
                    {
145 14ecd267 Panagiotis Kanavos
                        //Clear the status of already deleted files to avoid reprocessing
146 3c76f045 Panagiotis Kanavos
                        if (action.LocalFile != null)
147 3c76f045 Panagiotis Kanavos
                            this.StatusKeeper.ClearFileStatus(action.LocalFile.FullName);
148 14ecd267 Panagiotis Kanavos
                    }
149 14ecd267 Panagiotis Kanavos
                    else
150 14ecd267 Panagiotis Kanavos
                    {
151 14ecd267 Panagiotis Kanavos
                        switch (action.Action)
152 14ecd267 Panagiotis Kanavos
                        {
153 14ecd267 Panagiotis Kanavos
                            case CloudActionType.UploadUnconditional:
154 14ecd267 Panagiotis Kanavos
                                //Abort if the file was deleted before we reached this point
155 039a89e5 Panagiotis Kanavos
                                await UploadCloudFile(action);
156 14ecd267 Panagiotis Kanavos
                                break;
157 14ecd267 Panagiotis Kanavos
                            case CloudActionType.DownloadUnconditional:
158 039a89e5 Panagiotis Kanavos
                                await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
159 14ecd267 Panagiotis Kanavos
                                break;
160 14ecd267 Panagiotis Kanavos
                            case CloudActionType.RenameCloud:
161 e5b65606 Panagiotis Kanavos
                                var moveAction = (CloudMoveAction)action;
162 039a89e5 Panagiotis Kanavos
                                RenameCloudFile(accountInfo, moveAction);
163 14ecd267 Panagiotis Kanavos
                                break;
164 14ecd267 Panagiotis Kanavos
                            case CloudActionType.MustSynch:
165 14ecd267 Panagiotis Kanavos
                                if (!File.Exists(downloadPath) && !Directory.Exists(downloadPath))
166 14ecd267 Panagiotis Kanavos
                                {
167 039a89e5 Panagiotis Kanavos
                                    await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
168 14ecd267 Panagiotis Kanavos
                                }
169 14ecd267 Panagiotis Kanavos
                                else
170 14ecd267 Panagiotis Kanavos
                                {
171 039a89e5 Panagiotis Kanavos
                                    await SyncFiles(accountInfo, action);
172 14ecd267 Panagiotis Kanavos
                                }
173 14ecd267 Panagiotis Kanavos
                                break;
174 14ecd267 Panagiotis Kanavos
                        }
175 cfed7823 Panagiotis Kanavos
                    }
176 cfed7823 Panagiotis Kanavos
                    Log.InfoFormat("[ACTION] End Processing {0}:{1}->{2}", action.Action, action.LocalFile,
177 cfed7823 Panagiotis Kanavos
                                           action.CloudFile.Name);
178 cfed7823 Panagiotis Kanavos
                }
179 a0dcfcc9 Panagiotis Kanavos
                catch (WebException exc)
180 a0dcfcc9 Panagiotis Kanavos
                {
181 73cdd135 Panagiotis Kanavos
                    Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc);
182 a0dcfcc9 Panagiotis Kanavos
                }
183 cfed7823 Panagiotis Kanavos
                catch (OperationCanceledException)
184 cfed7823 Panagiotis Kanavos
                {
185 cfed7823 Panagiotis Kanavos
                    throw;
186 cfed7823 Panagiotis Kanavos
                }
187 73cdd135 Panagiotis Kanavos
                catch (DirectoryNotFoundException)
188 73cdd135 Panagiotis Kanavos
                {
189 73cdd135 Panagiotis Kanavos
                    Log.ErrorFormat("{0} : {1} -> {2}  failed because the directory was not found.\n Rescheduling a delete",
190 73cdd135 Panagiotis Kanavos
                        action.Action, action.LocalFile, action.CloudFile);
191 73cdd135 Panagiotis Kanavos
                    //Post a delete action for the missing file
192 e5b65606 Panagiotis Kanavos
                    Post(new CloudDeleteAction(action));
193 73cdd135 Panagiotis Kanavos
                }
194 73cdd135 Panagiotis Kanavos
                catch (FileNotFoundException)
195 cfed7823 Panagiotis Kanavos
                {
196 cfed7823 Panagiotis Kanavos
                    Log.ErrorFormat("{0} : {1} -> {2}  failed because the file was not found.\n Rescheduling a delete",
197 73cdd135 Panagiotis Kanavos
                        action.Action, action.LocalFile, action.CloudFile);
198 1bfc38f1 Panagiotis Kanavos
                    //Post a delete action for the missing file
199 1bfc38f1 Panagiotis Kanavos
                    Post(new CloudDeleteAction(action));
200 cfed7823 Panagiotis Kanavos
                }
201 cfed7823 Panagiotis Kanavos
                catch (Exception exc)
202 cfed7823 Panagiotis Kanavos
                {
203 cfed7823 Panagiotis Kanavos
                    Log.ErrorFormat("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}",
204 cfed7823 Panagiotis Kanavos
                                     action.Action, action.LocalFile, action.CloudFile, exc);
205 cfed7823 Panagiotis Kanavos
206 cfed7823 Panagiotis Kanavos
                    _agent.Post(action);
207 e5b65606 Panagiotis Kanavos
                }
208 e5b65606 Panagiotis Kanavos
                finally
209 e5b65606 Panagiotis Kanavos
                {
210 eae84ae8 Panagiotis Kanavos
                    UpdateStatus(PithosStatus.InSynch);                    
211 e5b65606 Panagiotis Kanavos
                }
212 cfed7823 Panagiotis Kanavos
            }
213 cfed7823 Panagiotis Kanavos
        }
214 cfed7823 Panagiotis Kanavos
215 eae84ae8 Panagiotis Kanavos
        private void UpdateStatus(PithosStatus status)
216 eae84ae8 Panagiotis Kanavos
        {
217 eae84ae8 Panagiotis Kanavos
            StatusKeeper.SetPithosStatus(status);
218 eae84ae8 Panagiotis Kanavos
            StatusNotification.Notify(new Notification());
219 eae84ae8 Panagiotis Kanavos
        }
220 eae84ae8 Panagiotis Kanavos
221 f3d080df Panagiotis Kanavos
        /// <summary>
222 f3d080df Panagiotis Kanavos
        /// Processes cloud delete actions
223 f3d080df Panagiotis Kanavos
        /// </summary>
224 f3d080df Panagiotis Kanavos
        /// <param name="action">The delete action to execute</param>
225 f3d080df Panagiotis Kanavos
        /// <returns></returns>
226 f3d080df Panagiotis Kanavos
        /// <remarks>
227 f3d080df Panagiotis Kanavos
        /// When a file/folder is deleted locally, we must delete it ASAP from the server and block any download
228 f3d080df Panagiotis Kanavos
        /// operations that may be in progress.
229 f3d080df Panagiotis Kanavos
        /// <para>
230 f3d080df Panagiotis Kanavos
        /// A separate agent is used to process deletes because the main agent may be busy with a long operation.
231 f3d080df Panagiotis Kanavos
        /// </para>
232 f3d080df Panagiotis Kanavos
        /// </remarks>
233 f3d080df Panagiotis Kanavos
        private async Task ProcessDelete(CloudDeleteAction action)
234 f3d080df Panagiotis Kanavos
        {
235 f3d080df Panagiotis Kanavos
            if (action == null)
236 f3d080df Panagiotis Kanavos
                throw new ArgumentNullException("action");
237 f3d080df Panagiotis Kanavos
            if (action.AccountInfo==null)
238 f3d080df Panagiotis Kanavos
                throw new ArgumentException("The action.AccountInfo is empty","action");
239 f3d080df Panagiotis Kanavos
            Contract.EndContractBlock();
240 f3d080df Panagiotis Kanavos
241 f3d080df Panagiotis Kanavos
            var accountInfo = action.AccountInfo;
242 f3d080df Panagiotis Kanavos
243 f3d080df Panagiotis Kanavos
            using (log4net.ThreadContext.Stacks["NETWORK"].Push("PROCESS"))
244 f3d080df Panagiotis Kanavos
            {                
245 f3d080df Panagiotis Kanavos
                Log.InfoFormat("[ACTION] Start Processing {0}", action);
246 f3d080df Panagiotis Kanavos
247 f3d080df Panagiotis Kanavos
                var cloudFile = action.CloudFile;
248 f3d080df Panagiotis Kanavos
249 f3d080df Panagiotis Kanavos
                try
250 f3d080df Panagiotis Kanavos
                {
251 f3d080df Panagiotis Kanavos
                    //Acquire a lock on the deleted file to prevent uploading/downloading operations from the normal
252 f3d080df Panagiotis Kanavos
                    //agent
253 f3d080df Panagiotis Kanavos
                    using (var gate = NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting))
254 f3d080df Panagiotis Kanavos
                    {
255 0b346191 Panagiotis Kanavos
256 039a89e5 Panagiotis Kanavos
                        //Add the file URL to the deleted files list
257 039a89e5 Panagiotis Kanavos
                        var key = GetFileKey(action.CloudFile);
258 0b346191 Panagiotis Kanavos
                        _deletedFiles[key] = DateTime.Now;
259 3c76f045 Panagiotis Kanavos
260 0b346191 Panagiotis Kanavos
                        _pauseAgent.Reset();
261 f3d080df Panagiotis Kanavos
                        // and then delete the file from the server
262 91b21852 Panagiotis Kanavos
                                DeleteCloudFile(accountInfo, cloudFile);
263 3c76f045 Panagiotis Kanavos
264 f3d080df Panagiotis Kanavos
                        Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile,
265 f3d080df Panagiotis Kanavos
                                       action.CloudFile.Name);
266 f3d080df Panagiotis Kanavos
                    }
267 f3d080df Panagiotis Kanavos
                }
268 f3d080df Panagiotis Kanavos
                catch (WebException exc)
269 f3d080df Panagiotis Kanavos
                {
270 f3d080df Panagiotis Kanavos
                    Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc);
271 f3d080df Panagiotis Kanavos
                }
272 f3d080df Panagiotis Kanavos
                catch (OperationCanceledException)
273 f3d080df Panagiotis Kanavos
                {
274 f3d080df Panagiotis Kanavos
                    throw;
275 f3d080df Panagiotis Kanavos
                }
276 f3d080df Panagiotis Kanavos
                catch (DirectoryNotFoundException)
277 f3d080df Panagiotis Kanavos
                {
278 f3d080df Panagiotis Kanavos
                    Log.ErrorFormat("{0} : {1} -> {2}  failed because the directory was not found.\n Rescheduling a delete",
279 f3d080df Panagiotis Kanavos
                        action.Action, action.LocalFile, action.CloudFile);
280 f3d080df Panagiotis Kanavos
                    //Repost a delete action for the missing file
281 0b346191 Panagiotis Kanavos
                    _deleteAgent.Post(action);
282 f3d080df Panagiotis Kanavos
                }
283 f3d080df Panagiotis Kanavos
                catch (FileNotFoundException)
284 f3d080df Panagiotis Kanavos
                {
285 f3d080df Panagiotis Kanavos
                    Log.ErrorFormat("{0} : {1} -> {2}  failed because the file was not found.\n Rescheduling a delete",
286 f3d080df Panagiotis Kanavos
                        action.Action, action.LocalFile, action.CloudFile);
287 f3d080df Panagiotis Kanavos
                    //Post a delete action for the missing file
288 f3d080df Panagiotis Kanavos
                    _deleteAgent.Post(action);
289 f3d080df Panagiotis Kanavos
                }
290 f3d080df Panagiotis Kanavos
                catch (Exception exc)
291 f3d080df Panagiotis Kanavos
                {
292 f3d080df Panagiotis Kanavos
                    Log.ErrorFormat("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}",
293 f3d080df Panagiotis Kanavos
                                     action.Action, action.LocalFile, action.CloudFile, exc);
294 f3d080df Panagiotis Kanavos
295 f3d080df Panagiotis Kanavos
                    _deleteAgent.Post(action);
296 0b346191 Panagiotis Kanavos
                }
297 0b346191 Panagiotis Kanavos
                finally
298 0b346191 Panagiotis Kanavos
                {
299 c99473ae Panagiotis Kanavos
                    //Set the event when all delete actions are processed
300 0b346191 Panagiotis Kanavos
                    if (_deleteAgent.InputCount == 0)
301 0b346191 Panagiotis Kanavos
                        _pauseAgent.Set();
302 0b346191 Panagiotis Kanavos
303 0b346191 Panagiotis Kanavos
                }
304 f3d080df Panagiotis Kanavos
            }
305 f3d080df Panagiotis Kanavos
        }
306 f3d080df Panagiotis Kanavos
307 039a89e5 Panagiotis Kanavos
        private static string GetFileKey(ObjectInfo info)
308 039a89e5 Panagiotis Kanavos
        {
309 039a89e5 Panagiotis Kanavos
            var key = String.Format("{0}/{1}/{2}", info.Account, info.Container,info.Name);
310 039a89e5 Panagiotis Kanavos
            return key;
311 039a89e5 Panagiotis Kanavos
        }
312 039a89e5 Panagiotis Kanavos
313 27361404 Panagiotis Kanavos
        private async Task SyncFiles(AccountInfo accountInfo,CloudAction action)
314 cfed7823 Panagiotis Kanavos
        {
315 c53aa229 Panagiotis Kanavos
            if (accountInfo == null)
316 c53aa229 Panagiotis Kanavos
                throw new ArgumentNullException("accountInfo");
317 cfed7823 Panagiotis Kanavos
            if (action==null)
318 cfed7823 Panagiotis Kanavos
                throw new ArgumentNullException("action");
319 cfed7823 Panagiotis Kanavos
            if (action.LocalFile==null)
320 cfed7823 Panagiotis Kanavos
                throw new ArgumentException("The action's local file is not specified","action");
321 cfed7823 Panagiotis Kanavos
            if (!Path.IsPathRooted(action.LocalFile.FullName))
322 cfed7823 Panagiotis Kanavos
                throw new ArgumentException("The action's local file path must be absolute","action");
323 cfed7823 Panagiotis Kanavos
            if (action.CloudFile== null)
324 cfed7823 Panagiotis Kanavos
                throw new ArgumentException("The action's cloud file is not specified", "action");
325 cfed7823 Panagiotis Kanavos
            Contract.EndContractBlock();
326 cfed7823 Panagiotis Kanavos
327 a64c87c8 Panagiotis Kanavos
            var localFile = action.LocalFile;
328 a64c87c8 Panagiotis Kanavos
            var cloudFile = action.CloudFile;
329 f3d080df Panagiotis Kanavos
            var downloadPath=action.LocalFile.GetProperCapitalization();
330 a27aa447 Panagiotis Kanavos
331 cfed7823 Panagiotis Kanavos
            var cloudHash = cloudFile.Hash.ToLower();
332 cfed7823 Panagiotis Kanavos
            var localHash = action.LocalHash.Value.ToLower();
333 cfed7823 Panagiotis Kanavos
            var topHash = action.TopHash.Value.ToLower();
334 cfed7823 Panagiotis Kanavos
335 cfed7823 Panagiotis Kanavos
            //Not enough to compare only the local hashes, also have to compare the tophashes
336 cfed7823 Panagiotis Kanavos
            
337 cfed7823 Panagiotis Kanavos
            //If any of the hashes match, we are done
338 cfed7823 Panagiotis Kanavos
            if ((cloudHash == localHash || cloudHash == topHash))
339 a64c87c8 Panagiotis Kanavos
            {
340 cfed7823 Panagiotis Kanavos
                Log.InfoFormat("Skipping {0}, hashes match",downloadPath);
341 cfed7823 Panagiotis Kanavos
                return;
342 cfed7823 Panagiotis Kanavos
            }
343 cfed7823 Panagiotis Kanavos
344 cfed7823 Panagiotis Kanavos
            //The hashes DON'T match. We need to sync
345 cfed7823 Panagiotis Kanavos
            var lastLocalTime = localFile.LastWriteTime;
346 cfed7823 Panagiotis Kanavos
            var lastUpTime = cloudFile.Last_Modified;
347 cfed7823 Panagiotis Kanavos
            
348 cfed7823 Panagiotis Kanavos
            //If the local file is newer upload it
349 cfed7823 Panagiotis Kanavos
            if (lastUpTime <= lastLocalTime)
350 cfed7823 Panagiotis Kanavos
            {
351 cfed7823 Panagiotis Kanavos
                //It probably means it was changed while the app was down                        
352 1bfc38f1 Panagiotis Kanavos
                UploadCloudFile(action);
353 cfed7823 Panagiotis Kanavos
            }
354 cfed7823 Panagiotis Kanavos
            else
355 cfed7823 Panagiotis Kanavos
            {
356 cfed7823 Panagiotis Kanavos
                //It the cloud file has a later date, it was modified by another user or computer.
357 cfed7823 Panagiotis Kanavos
                //We need to check the local file's status                
358 cfed7823 Panagiotis Kanavos
                var status = StatusKeeper.GetFileStatus(downloadPath);
359 cfed7823 Panagiotis Kanavos
                switch (status)
360 a64c87c8 Panagiotis Kanavos
                {
361 cfed7823 Panagiotis Kanavos
                    case FileStatus.Unchanged:                        
362 cfed7823 Panagiotis Kanavos
                        //If the local file's status is Unchanged, we can go on and download the newer cloud file
363 27361404 Panagiotis Kanavos
                        await DownloadCloudFile(accountInfo,cloudFile,downloadPath);
364 a64c87c8 Panagiotis Kanavos
                        break;
365 cfed7823 Panagiotis Kanavos
                    case FileStatus.Modified:
366 cfed7823 Panagiotis Kanavos
                        //If the local file is Modified, we may have a conflict. In this case we should mark the file as Conflict
367 cfed7823 Panagiotis Kanavos
                        //We can't ensure that a file modified online since the last time will appear as Modified, unless we 
368 cfed7823 Panagiotis Kanavos
                        //index all files before we start listening.                       
369 cfed7823 Panagiotis Kanavos
                    case FileStatus.Created:
370 cfed7823 Panagiotis Kanavos
                        //If the local file is Created, it means that the local and cloud files aren't related,
371 cfed7823 Panagiotis Kanavos
                        // yet they have the same name.
372 cfed7823 Panagiotis Kanavos
373 cfed7823 Panagiotis Kanavos
                        //In both cases we must mark the file as in conflict
374 cfed7823 Panagiotis Kanavos
                        ReportConflict(downloadPath);
375 a64c87c8 Panagiotis Kanavos
                        break;
376 cfed7823 Panagiotis Kanavos
                    default:
377 cfed7823 Panagiotis Kanavos
                        //Other cases should never occur. Mark them as Conflict as well but log a warning
378 cfed7823 Panagiotis Kanavos
                        ReportConflict(downloadPath);
379 cfed7823 Panagiotis Kanavos
                        Log.WarnFormat("Unexcepted status {0} for file {1}->{2}", status,
380 cfed7823 Panagiotis Kanavos
                                       downloadPath, action.CloudFile.Name);
381 a64c87c8 Panagiotis Kanavos
                        break;
382 a64c87c8 Panagiotis Kanavos
                }
383 cfed7823 Panagiotis Kanavos
            }
384 cfed7823 Panagiotis Kanavos
        }
385 cfed7823 Panagiotis Kanavos
386 cfed7823 Panagiotis Kanavos
        private void ReportConflict(string downloadPath)
387 cfed7823 Panagiotis Kanavos
        {
388 cfed7823 Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(downloadPath))
389 cfed7823 Panagiotis Kanavos
                throw new ArgumentNullException("downloadPath");
390 cfed7823 Panagiotis Kanavos
            Contract.EndContractBlock();
391 cfed7823 Panagiotis Kanavos
392 cfed7823 Panagiotis Kanavos
            StatusKeeper.SetFileOverlayStatus(downloadPath, FileOverlayStatus.Conflict);
393 eae84ae8 Panagiotis Kanavos
            UpdateStatus(PithosStatus.HasConflicts);
394 cfed7823 Panagiotis Kanavos
            var message = String.Format("Conflict detected for file {0}", downloadPath);
395 cfed7823 Panagiotis Kanavos
            Log.Warn(message);
396 cfed7823 Panagiotis Kanavos
            StatusNotification.NotifyChange(message, TraceLevel.Warning);
397 cfed7823 Panagiotis Kanavos
        }
398 cfed7823 Panagiotis Kanavos
399 9c4346c9 Panagiotis Kanavos
        public void Post(CloudAction cloudAction)
400 9c4346c9 Panagiotis Kanavos
        {
401 9c4346c9 Panagiotis Kanavos
            if (cloudAction == null)
402 9c4346c9 Panagiotis Kanavos
                throw new ArgumentNullException("cloudAction");
403 c53aa229 Panagiotis Kanavos
            if (cloudAction.AccountInfo==null)
404 c53aa229 Panagiotis Kanavos
                throw new ArgumentException("The CloudAction.AccountInfo is empty","cloudAction");
405 9c4346c9 Panagiotis Kanavos
            Contract.EndContractBlock();
406 f3d080df Panagiotis Kanavos
407 9d6d2f6e Panagiotis Kanavos
            _pauseAgent.Wait();
408 9d6d2f6e Panagiotis Kanavos
409 a27aa447 Panagiotis Kanavos
            //If the action targets a local file, add a treehash calculation
410 e81dd1f6 Panagiotis Kanavos
            if (!(cloudAction is CloudDeleteAction) && cloudAction.LocalFile as FileInfo != null)
411 a27aa447 Panagiotis Kanavos
            {
412 c53aa229 Panagiotis Kanavos
                var accountInfo = cloudAction.AccountInfo;
413 4f6d51d4 Panagiotis Kanavos
                var localFile = (FileInfo) cloudAction.LocalFile;
414 4f6d51d4 Panagiotis Kanavos
                if (localFile.Length > accountInfo.BlockSize)
415 4f6d51d4 Panagiotis Kanavos
                    cloudAction.TopHash =
416 4f6d51d4 Panagiotis Kanavos
                        new Lazy<string>(() => Signature.CalculateTreeHashAsync(localFile,
417 4f6d51d4 Panagiotis Kanavos
                                                                                accountInfo.BlockSize,
418 422c9598 Panagiotis Kanavos
                                                                                accountInfo.BlockHash, Settings.HashingParallelism).Result
419 4f6d51d4 Panagiotis Kanavos
                                                    .TopHash.ToHashString());
420 4f6d51d4 Panagiotis Kanavos
                else
421 cfed7823 Panagiotis Kanavos
                {
422 4f6d51d4 Panagiotis Kanavos
                    cloudAction.TopHash = new Lazy<string>(() => cloudAction.LocalHash.Value);
423 cfed7823 Panagiotis Kanavos
                }
424 4f6d51d4 Panagiotis Kanavos
            }
425 4f6d51d4 Panagiotis Kanavos
            else
426 4f6d51d4 Panagiotis Kanavos
            {
427 4f6d51d4 Panagiotis Kanavos
                //The hash for a directory is the empty string
428 4f6d51d4 Panagiotis Kanavos
                cloudAction.TopHash = new Lazy<string>(() => String.Empty);
429 a27aa447 Panagiotis Kanavos
            }
430 f3d080df Panagiotis Kanavos
            
431 f3d080df Panagiotis Kanavos
            if (cloudAction is CloudDeleteAction)
432 f3d080df Panagiotis Kanavos
                _deleteAgent.Post((CloudDeleteAction)cloudAction);
433 f3d080df Panagiotis Kanavos
            else
434 f3d080df Panagiotis Kanavos
                _agent.Post(cloudAction);
435 9c4346c9 Panagiotis Kanavos
        }
436 9c4346c9 Panagiotis Kanavos
437 5e31048f Panagiotis Kanavos
       /* class ObjectInfoByNameComparer:IEqualityComparer<ObjectInfo>
438 5ce54458 Panagiotis Kanavos
        {
439 5ce54458 Panagiotis Kanavos
            public bool Equals(ObjectInfo x, ObjectInfo y)
440 5ce54458 Panagiotis Kanavos
            {
441 5ce54458 Panagiotis Kanavos
                return x.Name.Equals(y.Name,StringComparison.InvariantCultureIgnoreCase);
442 5ce54458 Panagiotis Kanavos
            }
443 5ce54458 Panagiotis Kanavos
444 5ce54458 Panagiotis Kanavos
            public int GetHashCode(ObjectInfo obj)
445 5ce54458 Panagiotis Kanavos
            {
446 5ce54458 Panagiotis Kanavos
                return obj.Name.ToLower().GetHashCode();
447 5ce54458 Panagiotis Kanavos
            }
448 5e31048f Panagiotis Kanavos
        }*/
449 5ce54458 Panagiotis Kanavos
450 29a6b387 Panagiotis Kanavos
        public void SynchNow()
451 29a6b387 Panagiotis Kanavos
        {             
452 29a6b387 Panagiotis Kanavos
            if (_tcs!=null)
453 eae84ae8 Panagiotis Kanavos
                _tcs.TrySetResult(true);
454 29a6b387 Panagiotis Kanavos
            else
455 29a6b387 Panagiotis Kanavos
            {
456 29a6b387 Panagiotis Kanavos
                //TODO: This may be OK for testing purposes, but we have no guarantee that it will
457 29a6b387 Panagiotis Kanavos
                //work properly in production
458 29a6b387 Panagiotis Kanavos
                PollRemoteFiles(repeat:false);
459 29a6b387 Panagiotis Kanavos
            }
460 29a6b387 Panagiotis Kanavos
        }
461 c53aa229 Panagiotis Kanavos
462 a64c87c8 Panagiotis Kanavos
        //Remote files are polled periodically. Any changes are processed
463 29a6b387 Panagiotis Kanavos
        public async Task PollRemoteFiles(DateTime? since = null,bool repeat=true)
464 29a6b387 Panagiotis Kanavos
        {
465 eae84ae8 Panagiotis Kanavos
            UpdateStatus(PithosStatus.Syncing);
466 eae84ae8 Panagiotis Kanavos
            StatusNotification.Notify(new PollNotification());
467 73cdd135 Panagiotis Kanavos
468 73cdd135 Panagiotis Kanavos
            using (log4net.ThreadContext.Stacks["Retrieve Remote"].Push("All accounts"))
469 c53aa229 Panagiotis Kanavos
            {
470 025046f1 Panagiotis Kanavos
                //If this poll fails, we will retry with the same since value
471 025046f1 Panagiotis Kanavos
                var nextSince = since;
472 73cdd135 Panagiotis Kanavos
                try
473 c53aa229 Panagiotis Kanavos
                {
474 c53aa229 Panagiotis Kanavos
                    //Next time we will check for all changes since the current check minus 1 second
475 c53aa229 Panagiotis Kanavos
                    //This is done to ensure there are no discrepancies due to clock differences
476 025046f1 Panagiotis Kanavos
                    DateTime current = DateTime.Now.AddSeconds(-1);
477 c53aa229 Panagiotis Kanavos
478 73cdd135 Panagiotis Kanavos
                    var tasks = from accountInfo in _accounts
479 73cdd135 Panagiotis Kanavos
                                select ProcessAccountFiles(accountInfo, since);
480 73cdd135 Panagiotis Kanavos
481 4f6d51d4 Panagiotis Kanavos
                    await TaskEx.WhenAll(tasks.ToList());
482 025046f1 Panagiotis Kanavos
                                        
483 540b8cf8 Panagiotis Kanavos
                    _firstPoll = false;
484 025046f1 Panagiotis Kanavos
                    //Reschedule the poll with the current timestamp as a "since" value
485 29a6b387 Panagiotis Kanavos
                    if (repeat)
486 025046f1 Panagiotis Kanavos
                        nextSince = current;
487 025046f1 Panagiotis Kanavos
                    else
488 025046f1 Panagiotis Kanavos
                        return;
489 c53aa229 Panagiotis Kanavos
                }
490 73cdd135 Panagiotis Kanavos
                catch (Exception ex)
491 73cdd135 Panagiotis Kanavos
                {
492 73cdd135 Panagiotis Kanavos
                    Log.ErrorFormat("Error while processing accounts\r\n{0}",ex);
493 025046f1 Panagiotis Kanavos
                    //In case of failure retry with the same "since" value
494 73cdd135 Panagiotis Kanavos
                }
495 eae84ae8 Panagiotis Kanavos
                
496 eae84ae8 Panagiotis Kanavos
                UpdateStatus(PithosStatus.InSynch);
497 025046f1 Panagiotis Kanavos
                //Wait for the polling interval to pass or the Manual flat to be toggled
498 025046f1 Panagiotis Kanavos
                nextSince = await WaitForScheduledOrManualPoll(nextSince);
499 025046f1 Panagiotis Kanavos
500 025046f1 Panagiotis Kanavos
                PollRemoteFiles(nextSince);
501 73cdd135 Panagiotis Kanavos
502 73cdd135 Panagiotis Kanavos
            }
503 c53aa229 Panagiotis Kanavos
        }
504 c53aa229 Panagiotis Kanavos
505 025046f1 Panagiotis Kanavos
        private async Task<DateTime?> WaitForScheduledOrManualPoll(DateTime? since)
506 025046f1 Panagiotis Kanavos
        {            
507 025046f1 Panagiotis Kanavos
            _tcs = new TaskCompletionSource<bool>();
508 025046f1 Panagiotis Kanavos
            var wait = TaskEx.Delay(TimeSpan.FromSeconds(Settings.PollingInterval), _agent.CancellationToken);
509 025046f1 Panagiotis Kanavos
            var signaledTask = await TaskEx.WhenAny(_tcs.Task, wait);
510 025046f1 Panagiotis Kanavos
            //If polling is signalled by SynchNow, ignore the since tag
511 025046f1 Panagiotis Kanavos
            if (signaledTask is Task<bool>)
512 025046f1 Panagiotis Kanavos
                return null;
513 025046f1 Panagiotis Kanavos
            return since;
514 025046f1 Panagiotis Kanavos
        }
515 025046f1 Panagiotis Kanavos
516 73cdd135 Panagiotis Kanavos
        public async Task ProcessAccountFiles(AccountInfo accountInfo,DateTime? since=null)
517 a64c87c8 Panagiotis Kanavos
        {   
518 c53aa229 Panagiotis Kanavos
            if (accountInfo==null)
519 c53aa229 Panagiotis Kanavos
                throw new ArgumentNullException("accountInfo");
520 c53aa229 Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(accountInfo.AccountPath))
521 c53aa229 Panagiotis Kanavos
                throw new ArgumentException("The AccountInfo.AccountPath is empty","accountInfo");
522 a64c87c8 Panagiotis Kanavos
            Contract.EndContractBlock();
523 9c4346c9 Panagiotis Kanavos
524 e5b65606 Panagiotis Kanavos
525 c53aa229 Panagiotis Kanavos
            using (log4net.ThreadContext.Stacks["Retrieve Remote"].Push(accountInfo.UserName))
526 cfed7823 Panagiotis Kanavos
            {
527 303596f9 Panagiotis Kanavos
                await _pauseAgent.WaitAsync();
528 c99473ae Panagiotis Kanavos
529 c53aa229 Panagiotis Kanavos
                Log.Info("Scheduled");
530 422c9598 Panagiotis Kanavos
                var client = new CloudFilesClient(accountInfo)
531 422c9598 Panagiotis Kanavos
                                 {
532 422c9598 Panagiotis Kanavos
                                     Proxy = PithosMonitor.ProxyFromSettings(this.Settings)
533 422c9598 Panagiotis Kanavos
                                 };
534 0af3141d Panagiotis Kanavos
535 5750d7cc Panagiotis Kanavos
                var containers = client.ListContainers(accountInfo.UserName);
536 c99473ae Panagiotis Kanavos
537 c99473ae Panagiotis Kanavos
538 5750d7cc Panagiotis Kanavos
                CreateContainerFolders(accountInfo, containers);
539 5750d7cc Panagiotis Kanavos
540 73cdd135 Panagiotis Kanavos
                try
541 73cdd135 Panagiotis Kanavos
                {
542 303596f9 Panagiotis Kanavos
                    await _pauseAgent.WaitAsync();
543 540b8cf8 Panagiotis Kanavos
                    //Get the poll time now. We may miss some deletions but it's better to keep a file that was deleted
544 91b21852 Panagiotis Kanavos
                    //than delete a file that was created while we were executing the poll                    
545 540b8cf8 Panagiotis Kanavos
                    var pollTime = DateTime.Now;
546 73cdd135 Panagiotis Kanavos
                    
547 73cdd135 Panagiotis Kanavos
                    //Get the list of server objects changed since the last check
548 73cdd135 Panagiotis Kanavos
                    //The name of the container is passed as state in order to create a dictionary of tasks in a subsequent step
549 add4f744 Panagiotis Kanavos
                    var listObjects = (from container in containers
550 73cdd135 Panagiotis Kanavos
                                      select  Task<IList<ObjectInfo>>.Factory.StartNew(_ =>
551 add4f744 Panagiotis Kanavos
                                            client.ListObjects(accountInfo.UserName,container.Name, since),container.Name)).ToList();
552 0af3141d Panagiotis Kanavos
553 add4f744 Panagiotis Kanavos
                    var listShared = Task<IList<ObjectInfo>>.Factory.StartNew(_ => client.ListSharedObjects(since), "shared");
554 add4f744 Panagiotis Kanavos
                    listObjects.Add(listShared);
555 73cdd135 Panagiotis Kanavos
                    var listTasks = await Task.Factory.WhenAll(listObjects.ToArray());
556 9c4346c9 Panagiotis Kanavos
557 5120f3cb Panagiotis Kanavos
                    using (log4net.ThreadContext.Stacks["SCHEDULE"].Push("Process Results"))
558 cfed7823 Panagiotis Kanavos
                    {
559 73cdd135 Panagiotis Kanavos
                        var dict = listTasks.ToDictionary(t => t.AsyncState);
560 73cdd135 Panagiotis Kanavos
561 ec6f3895 Panagiotis Kanavos
                        //Get all non-trash objects. Remember, the container name is stored in AsyncState
562 73cdd135 Panagiotis Kanavos
                        var remoteObjects = from objectList in listTasks
563 73cdd135 Panagiotis Kanavos
                                            where (string) objectList.AsyncState != "trash"
564 ec6f3895 Panagiotis Kanavos
                                            from obj in objectList.Result
565 ec6f3895 Panagiotis Kanavos
                                            select obj;
566 73cdd135 Panagiotis Kanavos
567 540b8cf8 Panagiotis Kanavos
                        //TODO: Change the way deleted objects are detected.
568 540b8cf8 Panagiotis Kanavos
                        //The list operation returns all existing objects so we could detect deleted remote objects
569 540b8cf8 Panagiotis Kanavos
                        //by detecting objects that exist only locally. There are several cases where this is NOT the case:
570 540b8cf8 Panagiotis Kanavos
                        //1.    The first time the application runs, as there may be files that were added while 
571 540b8cf8 Panagiotis Kanavos
                        //      the application was down.
572 540b8cf8 Panagiotis Kanavos
                        //2.    An object that is currently being uploaded will not appear in the remote list
573 540b8cf8 Panagiotis Kanavos
                        //      until the upload finishes.
574 540b8cf8 Panagiotis Kanavos
                        //      SOLUTION 1: Check the upload/download queue for the file
575 540b8cf8 Panagiotis Kanavos
                        //      SOLUTION 2: Check the SQLite states for the file's entry. If it is being uploaded, 
576 540b8cf8 Panagiotis Kanavos
                        //          or its last modification was after the current poll, don't delete it. This way we don't
577 540b8cf8 Panagiotis Kanavos
                        //          delete objects whose upload finished too late to be included in the list.
578 540b8cf8 Panagiotis Kanavos
                        //We need to detect and protect against such situations
579 540b8cf8 Panagiotis Kanavos
                        //TODO: Does FileState have a LastModification field?
580 540b8cf8 Panagiotis Kanavos
                        //TODO: How do we update the LastModification field? Do we need to add SQLite triggers?
581 540b8cf8 Panagiotis Kanavos
                        //      Do we need to use a proper SQLite schema?
582 540b8cf8 Panagiotis Kanavos
                        //      We can create a trigger with 
583 540b8cf8 Panagiotis Kanavos
                        // CREATE TRIGGER IF NOT EXISTS update_last_modified UPDATE ON FileState FOR EACH ROW
584 540b8cf8 Panagiotis Kanavos
                        //  BEGIN
585 540b8cf8 Panagiotis Kanavos
                        //      UPDATE FileState SET LastModification=datetime('now')  WHERE Id=old.Id;
586 540b8cf8 Panagiotis Kanavos
                        //  END;
587 540b8cf8 Panagiotis Kanavos
                        //
588 540b8cf8 Panagiotis Kanavos
                        //NOTE: Some files may have been deleted remotely while the application was down. 
589 540b8cf8 Panagiotis Kanavos
                        //  We DO have to delete those files. Checking the trash makes it easy to detect them,
590 540b8cf8 Panagiotis Kanavos
                        //  Otherwise, we can't be really sure whether we need to upload or delete 
591 540b8cf8 Panagiotis Kanavos
                        //  the local-only files.
592 540b8cf8 Panagiotis Kanavos
                        //  SOLUTION 1: Ask the user when such a local-only file is detected during the first poll.
593 540b8cf8 Panagiotis Kanavos
                        //  SOLUTION 2: Mark conflict and ask the user as in #1
594 540b8cf8 Panagiotis Kanavos
595 ec6f3895 Panagiotis Kanavos
                        var trashObjects = dict["trash"].Result;
596 add4f744 Panagiotis Kanavos
                        var sharedObjects = dict["shared"].Result;
597 cfed7823 Panagiotis Kanavos
598 cfed7823 Panagiotis Kanavos
                        //Items with the same name, hash may be both in the container and the trash
599 cfed7823 Panagiotis Kanavos
                        //Don't delete items that exist in the container
600 cfed7823 Panagiotis Kanavos
                        var realTrash = from trash in trashObjects
601 73cdd135 Panagiotis Kanavos
                                        where
602 73cdd135 Panagiotis Kanavos
                                            !remoteObjects.Any(
603 73cdd135 Panagiotis Kanavos
                                                info => info.Name == trash.Name && info.Hash == trash.Hash)
604 cfed7823 Panagiotis Kanavos
                                        select trash;
605 540b8cf8 Panagiotis Kanavos
                        ProcessTrashedFiles(accountInfo, realTrash);
606 cfed7823 Panagiotis Kanavos
607 cfed7823 Panagiotis Kanavos
608 add4f744 Panagiotis Kanavos
                        var cleanRemotes = (from info in remoteObjects.Union(sharedObjects)
609 cfed7823 Panagiotis Kanavos
                                     let name = info.Name
610 cfed7823 Panagiotis Kanavos
                                     where !name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase) &&
611 73cdd135 Panagiotis Kanavos
                                           !name.StartsWith(FolderConstants.CacheFolder + "/",
612 73cdd135 Panagiotis Kanavos
                                                            StringComparison.InvariantCultureIgnoreCase)
613 133f83c2 Panagiotis Kanavos
                                     select info).ToList();
614 cfed7823 Panagiotis Kanavos
615 a0622735 Panagiotis Kanavos
                        var differencer = _differencer.PostSnapshot(accountInfo, cleanRemotes);
616 a0622735 Panagiotis Kanavos
                        
617 422c9598 Panagiotis Kanavos
                        ProcessDeletedFiles(accountInfo, differencer.Deleted, pollTime);
618 cfed7823 Panagiotis Kanavos
619 cfed7823 Panagiotis Kanavos
                        //Create a list of actions from the remote files
620 422c9598 Panagiotis Kanavos
                        var allActions = ChangesToActions(accountInfo, differencer.Changed)
621 422c9598 Panagiotis Kanavos
                                        .Union(
622 422c9598 Panagiotis Kanavos
                                        CreatesToActions(accountInfo,differencer.Created));
623 540b8cf8 Panagiotis Kanavos
624 133f83c2 Panagiotis Kanavos
                        
625 540b8cf8 Panagiotis Kanavos
                        //var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
626 73cdd135 Panagiotis Kanavos
627 cfed7823 Panagiotis Kanavos
                        //And remove those that are already being processed by the agent
628 cfed7823 Panagiotis Kanavos
                        var distinctActions = allActions
629 cfed7823 Panagiotis Kanavos
                            .Except(_agent.GetEnumerable(), new PithosMonitor.LocalFileComparer())
630 cfed7823 Panagiotis Kanavos
                            .ToList();
631 cfed7823 Panagiotis Kanavos
632 cfed7823 Panagiotis Kanavos
                        //Queue all the actions
633 cfed7823 Panagiotis Kanavos
                        foreach (var message in distinctActions)
634 cfed7823 Panagiotis Kanavos
                        {
635 cfed7823 Panagiotis Kanavos
                            Post(message);
636 cfed7823 Panagiotis Kanavos
                        }
637 9c4346c9 Panagiotis Kanavos
638 73cdd135 Panagiotis Kanavos
                        Log.Info("[LISTENER] End Processing");
639 cfed7823 Panagiotis Kanavos
                    }
640 73cdd135 Panagiotis Kanavos
                }
641 73cdd135 Panagiotis Kanavos
                catch (Exception ex)
642 73cdd135 Panagiotis Kanavos
                {
643 73cdd135 Panagiotis Kanavos
                    Log.ErrorFormat("[FAIL] ListObjects for{0} in ProcessRemoteFiles with {1}", accountInfo.UserName, ex);
644 73cdd135 Panagiotis Kanavos
                    return;
645 73cdd135 Panagiotis Kanavos
                }
646 73cdd135 Panagiotis Kanavos
647 73cdd135 Panagiotis Kanavos
                Log.Info("[LISTENER] Finished");
648 cfed7823 Panagiotis Kanavos
649 cfed7823 Panagiotis Kanavos
            }
650 cfed7823 Panagiotis Kanavos
        }
651 9c4346c9 Panagiotis Kanavos
652 a0622735 Panagiotis Kanavos
        AccountsDifferencer _differencer= new AccountsDifferencer();
653 422c9598 Panagiotis Kanavos
654 422c9598 Panagiotis Kanavos
/*
655 2edb4807 Panagiotis Kanavos
        Dictionary<string, List<ObjectInfo>> _currentSnapshot = new Dictionary<string, List<ObjectInfo>>();
656 2edb4807 Panagiotis Kanavos
        Dictionary<string, List<ObjectInfo>> _previousSnapshot = new Dictionary<string, List<ObjectInfo>>();
657 422c9598 Panagiotis Kanavos
*/
658 2edb4807 Panagiotis Kanavos
659 540b8cf8 Panagiotis Kanavos
        /// <summary>
660 540b8cf8 Panagiotis Kanavos
        /// Deletes local files that are not found in the list of cloud files
661 540b8cf8 Panagiotis Kanavos
        /// </summary>
662 540b8cf8 Panagiotis Kanavos
        /// <param name="accountInfo"></param>
663 540b8cf8 Panagiotis Kanavos
        /// <param name="cloudFiles"></param>
664 540b8cf8 Panagiotis Kanavos
        /// <param name="pollTime"></param>
665 540b8cf8 Panagiotis Kanavos
        private void ProcessDeletedFiles(AccountInfo accountInfo, IEnumerable<ObjectInfo> cloudFiles, DateTime pollTime)
666 540b8cf8 Panagiotis Kanavos
        {
667 540b8cf8 Panagiotis Kanavos
            if (accountInfo == null)
668 540b8cf8 Panagiotis Kanavos
                throw new ArgumentNullException("accountInfo");
669 540b8cf8 Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(accountInfo.AccountPath))
670 540b8cf8 Panagiotis Kanavos
                throw new ArgumentException("The AccountInfo.AccountPath is empty", "accountInfo");
671 540b8cf8 Panagiotis Kanavos
            if (cloudFiles == null)
672 540b8cf8 Panagiotis Kanavos
                throw new ArgumentNullException("cloudFiles");
673 540b8cf8 Panagiotis Kanavos
            Contract.EndContractBlock();
674 540b8cf8 Panagiotis Kanavos
675 9c6d3193 Panagiotis Kanavos
            //On the first run
676 9c6d3193 Panagiotis Kanavos
            if (_firstPoll)
677 540b8cf8 Panagiotis Kanavos
            {
678 2edb4807 Panagiotis Kanavos
                //Only consider files that are not being modified, ie they are in the Unchanged state            
679 2edb4807 Panagiotis Kanavos
                var deleteCandidates = FileState.Queryable.Where(state =>
680 2edb4807 Panagiotis Kanavos
                    state.FilePath.StartsWith(accountInfo.AccountPath)
681 2edb4807 Panagiotis Kanavos
                    && state.FileStatus == FileStatus.Unchanged).ToList();
682 2edb4807 Panagiotis Kanavos
683 2edb4807 Panagiotis Kanavos
684 2edb4807 Panagiotis Kanavos
                //TODO: filesToDelete must take into account the Others container            
685 2edb4807 Panagiotis Kanavos
                var filesToDelete = (from deleteCandidate in deleteCandidates
686 2edb4807 Panagiotis Kanavos
                                         let localFile = FileInfoExtensions.FromPath(deleteCandidate.FilePath)
687 2edb4807 Panagiotis Kanavos
                                         let relativeFilePath = localFile.AsRelativeTo(accountInfo.AccountPath)
688 2edb4807 Panagiotis Kanavos
                                     where
689 2edb4807 Panagiotis Kanavos
                                         !cloudFiles.Any(r => r.RelativeUrlToFilePath(accountInfo.UserName) == relativeFilePath)
690 2edb4807 Panagiotis Kanavos
                                     select localFile).ToList();
691 2edb4807 Panagiotis Kanavos
            
692 2edb4807 Panagiotis Kanavos
693 2edb4807 Panagiotis Kanavos
694 9c6d3193 Panagiotis Kanavos
                //Set the status of missing files to Conflict
695 9c6d3193 Panagiotis Kanavos
                foreach (var item in filesToDelete)
696 97f51ab0 Panagiotis Kanavos
                {
697 df6e9afc Panagiotis Kanavos
                    //Try to acquire a gate on the file, to take into account files that have been dequeued
698 df6e9afc Panagiotis Kanavos
                    //and are being processed
699 df6e9afc Panagiotis Kanavos
                    using (var gate = NetworkGate.Acquire(item.FullName, NetworkOperation.Deleting))
700 df6e9afc Panagiotis Kanavos
                    {
701 df6e9afc Panagiotis Kanavos
                        if (gate.Failed)
702 df6e9afc Panagiotis Kanavos
                            continue;
703 df6e9afc Panagiotis Kanavos
                        StatusKeeper.SetFileState(item.FullName, FileStatus.Conflict, FileOverlayStatus.Deleted);
704 df6e9afc Panagiotis Kanavos
                    }
705 97f51ab0 Panagiotis Kanavos
                }
706 eae84ae8 Panagiotis Kanavos
                UpdateStatus(PithosStatus.HasConflicts);
707 9c6d3193 Panagiotis Kanavos
                StatusNotification.NotifyConflicts(filesToDelete, String.Format("{0} local files are missing from Pithos, possibly because they were deleted",filesToDelete.Count));
708 2edb4807 Panagiotis Kanavos
                StatusNotification.NotifyForFiles(filesToDelete, String.Format("{0} files were deleted", filesToDelete.Count), TraceLevel.Info);
709 540b8cf8 Panagiotis Kanavos
            }
710 9c6d3193 Panagiotis Kanavos
            else
711 9c6d3193 Panagiotis Kanavos
            {
712 2edb4807 Panagiotis Kanavos
                var deletedFiles = new List<FileSystemInfo>();
713 422c9598 Panagiotis Kanavos
                foreach (var objectInfo in cloudFiles)
714 9c6d3193 Panagiotis Kanavos
                {
715 2edb4807 Panagiotis Kanavos
                    var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
716 2edb4807 Panagiotis Kanavos
                    var item = GetFileAgent(accountInfo).GetFileSystemInfo(relativePath);
717 d9b1cbeb Panagiotis Kanavos
                    if (item.Exists)
718 d9b1cbeb Panagiotis Kanavos
                    {
719 df6e9afc Panagiotis Kanavos
                        //Try to acquire a gate on the file, to take into account files that have been dequeued
720 df6e9afc Panagiotis Kanavos
                        //and are being processed
721 df6e9afc Panagiotis Kanavos
                        //TODO: The gate is not enough. Perhaps we need to keep a journal of processed files and check against
722 df6e9afc Panagiotis Kanavos
                        //that as well.
723 2edb4807 Panagiotis Kanavos
/*
724 df6e9afc Panagiotis Kanavos
                        using (var gate = NetworkGate.Acquire(item.FullName, NetworkOperation.Deleting))
725 d9b1cbeb Panagiotis Kanavos
                        {
726 df6e9afc Panagiotis Kanavos
                            if (gate.Failed)
727 df6e9afc Panagiotis Kanavos
                                continue;
728 2edb4807 Panagiotis Kanavos
*/
729 df6e9afc Panagiotis Kanavos
                            if ((item.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
730 df6e9afc Panagiotis Kanavos
                            {
731 df6e9afc Panagiotis Kanavos
                                item.Attributes = item.Attributes & ~FileAttributes.ReadOnly;
732 d9b1cbeb Panagiotis Kanavos
733 df6e9afc Panagiotis Kanavos
                            }
734 df6e9afc Panagiotis Kanavos
                            item.Delete();
735 2edb4807 Panagiotis Kanavos
                            DateTime lastDate;
736 2edb4807 Panagiotis Kanavos
                            _lastSeen.TryRemove(item.FullName, out lastDate);
737 2edb4807 Panagiotis Kanavos
                            deletedFiles.Add(item);
738 2edb4807 Panagiotis Kanavos
/*
739 d9b1cbeb Panagiotis Kanavos
                        }
740 2edb4807 Panagiotis Kanavos
*/
741 d9b1cbeb Panagiotis Kanavos
                    }
742 422c9598 Panagiotis Kanavos
                    StatusKeeper.SetFileState(item.FullName, FileStatus.Deleted, FileOverlayStatus.Deleted);                    
743 9c6d3193 Panagiotis Kanavos
                }
744 2edb4807 Panagiotis Kanavos
                StatusNotification.NotifyForFiles(deletedFiles, String.Format("{0} files were deleted", deletedFiles.Count), TraceLevel.Info);
745 9c6d3193 Panagiotis Kanavos
            }
746 9c6d3193 Panagiotis Kanavos
747 540b8cf8 Panagiotis Kanavos
        }
748 540b8cf8 Panagiotis Kanavos
749 4f6d51d4 Panagiotis Kanavos
        private static void CreateContainerFolders(AccountInfo accountInfo, IEnumerable<ContainerInfo> containers)
750 5750d7cc Panagiotis Kanavos
        {
751 5750d7cc Panagiotis Kanavos
            var containerPaths = from container in containers
752 5750d7cc Panagiotis Kanavos
                                 let containerPath = Path.Combine(accountInfo.AccountPath, container.Name)
753 5750d7cc Panagiotis Kanavos
                                 where container.Name != FolderConstants.TrashContainer && !Directory.Exists(containerPath)
754 5750d7cc Panagiotis Kanavos
                                 select containerPath;
755 5750d7cc Panagiotis Kanavos
756 5750d7cc Panagiotis Kanavos
            foreach (var path in containerPaths)
757 5750d7cc Panagiotis Kanavos
            {
758 5750d7cc Panagiotis Kanavos
                Directory.CreateDirectory(path);
759 5750d7cc Panagiotis Kanavos
            }
760 5750d7cc Panagiotis Kanavos
        }
761 5750d7cc Panagiotis Kanavos
762 cfed7823 Panagiotis Kanavos
        //Creates an appropriate action for each server file
763 422c9598 Panagiotis Kanavos
        private IEnumerable<CloudAction> ChangesToActions(AccountInfo accountInfo,IEnumerable<ObjectInfo> changes)
764 422c9598 Panagiotis Kanavos
        {
765 422c9598 Panagiotis Kanavos
            if (changes==null)
766 422c9598 Panagiotis Kanavos
                throw new ArgumentNullException();
767 422c9598 Panagiotis Kanavos
            Contract.EndContractBlock();
768 422c9598 Panagiotis Kanavos
            var fileAgent = GetFileAgent(accountInfo);
769 422c9598 Panagiotis Kanavos
770 422c9598 Panagiotis Kanavos
            //In order to avoid multiple iterations over the files, we iterate only once
771 422c9598 Panagiotis Kanavos
            //over the remote files
772 422c9598 Panagiotis Kanavos
            foreach (var objectInfo in changes)
773 422c9598 Panagiotis Kanavos
            {
774 422c9598 Panagiotis Kanavos
                var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
775 422c9598 Panagiotis Kanavos
                //and remove any matching objects from the list, adding them to the commonObjects list
776 422c9598 Panagiotis Kanavos
                if (fileAgent.Exists(relativePath))
777 422c9598 Panagiotis Kanavos
                {
778 422c9598 Panagiotis Kanavos
                    //If a directory object already exists, we don't need to perform any other action                    
779 422c9598 Panagiotis Kanavos
                    var localFile = fileAgent.GetFileSystemInfo(relativePath);
780 422c9598 Panagiotis Kanavos
                    if (objectInfo.Content_Type == @"application/directory" && localFile is DirectoryInfo)
781 422c9598 Panagiotis Kanavos
                        continue;
782 422c9598 Panagiotis Kanavos
                    using (new SessionScope(FlushAction.Never))
783 422c9598 Panagiotis Kanavos
                    {
784 422c9598 Panagiotis Kanavos
                        var state =  StatusKeeper.GetStateByFilePath(localFile.FullName);
785 422c9598 Panagiotis Kanavos
                        _lastSeen[localFile.FullName] = DateTime.Now;
786 422c9598 Panagiotis Kanavos
                        //FileState.FindByFilePath(localFile.FullName);
787 422c9598 Panagiotis Kanavos
                        //Common files should be checked on a per-case basis to detect differences, which is newer
788 422c9598 Panagiotis Kanavos
789 422c9598 Panagiotis Kanavos
                        yield return new CloudAction(accountInfo, CloudActionType.MustSynch,
790 422c9598 Panagiotis Kanavos
                                                     localFile, objectInfo, state, accountInfo.BlockSize,
791 422c9598 Panagiotis Kanavos
                                                     accountInfo.BlockHash);
792 422c9598 Panagiotis Kanavos
                    }
793 422c9598 Panagiotis Kanavos
                }
794 422c9598 Panagiotis Kanavos
                else
795 422c9598 Panagiotis Kanavos
                {
796 422c9598 Panagiotis Kanavos
                    //Remote files should be downloaded
797 422c9598 Panagiotis Kanavos
                    yield return new CloudDownloadAction(accountInfo,objectInfo);
798 422c9598 Panagiotis Kanavos
                }
799 422c9598 Panagiotis Kanavos
            }            
800 422c9598 Panagiotis Kanavos
        }
801 422c9598 Panagiotis Kanavos
802 422c9598 Panagiotis Kanavos
        private IEnumerable<CloudAction> CreatesToActions(AccountInfo accountInfo,IEnumerable<ObjectInfo> creates)
803 422c9598 Panagiotis Kanavos
        {
804 422c9598 Panagiotis Kanavos
            if (creates==null)
805 422c9598 Panagiotis Kanavos
                throw new ArgumentNullException();
806 422c9598 Panagiotis Kanavos
            Contract.EndContractBlock();
807 422c9598 Panagiotis Kanavos
            var fileAgent = GetFileAgent(accountInfo);
808 422c9598 Panagiotis Kanavos
809 422c9598 Panagiotis Kanavos
            //In order to avoid multiple iterations over the files, we iterate only once
810 422c9598 Panagiotis Kanavos
            //over the remote files
811 422c9598 Panagiotis Kanavos
            foreach (var objectInfo in creates)
812 422c9598 Panagiotis Kanavos
            {
813 422c9598 Panagiotis Kanavos
                var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
814 422c9598 Panagiotis Kanavos
                //and remove any matching objects from the list, adding them to the commonObjects list
815 422c9598 Panagiotis Kanavos
                if (fileAgent.Exists(relativePath))
816 422c9598 Panagiotis Kanavos
                {
817 422c9598 Panagiotis Kanavos
                    //If the object already exists, we probably have a conflict
818 422c9598 Panagiotis Kanavos
                    //If a directory object already exists, we don't need to perform any other action                    
819 422c9598 Panagiotis Kanavos
                    var localFile = fileAgent.GetFileSystemInfo(relativePath);
820 422c9598 Panagiotis Kanavos
                    StatusKeeper.SetFileState(localFile.FullName,FileStatus.Conflict,FileOverlayStatus.Conflict);
821 422c9598 Panagiotis Kanavos
                }
822 422c9598 Panagiotis Kanavos
                else
823 422c9598 Panagiotis Kanavos
                {
824 422c9598 Panagiotis Kanavos
                    //Remote files should be downloaded
825 422c9598 Panagiotis Kanavos
                    yield return new CloudDownloadAction(accountInfo,objectInfo);
826 422c9598 Panagiotis Kanavos
                }
827 422c9598 Panagiotis Kanavos
            }            
828 422c9598 Panagiotis Kanavos
        }
829 422c9598 Panagiotis Kanavos
830 422c9598 Panagiotis Kanavos
        //Creates an appropriate action for each server file
831 422c9598 Panagiotis Kanavos
/*
832 c53aa229 Panagiotis Kanavos
        private IEnumerable<CloudAction> ObjectsToActions(AccountInfo accountInfo,IEnumerable<ObjectInfo> remote)
833 cfed7823 Panagiotis Kanavos
        {
834 cfed7823 Panagiotis Kanavos
            if (remote==null)
835 cfed7823 Panagiotis Kanavos
                throw new ArgumentNullException();
836 cfed7823 Panagiotis Kanavos
            Contract.EndContractBlock();
837 c28a075a Panagiotis Kanavos
            var fileAgent = GetFileAgent(accountInfo);
838 9c4346c9 Panagiotis Kanavos
839 cfed7823 Panagiotis Kanavos
            //In order to avoid multiple iterations over the files, we iterate only once
840 cfed7823 Panagiotis Kanavos
            //over the remote files
841 cfed7823 Panagiotis Kanavos
            foreach (var objectInfo in remote)
842 9c4346c9 Panagiotis Kanavos
            {
843 c53aa229 Panagiotis Kanavos
                var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
844 cfed7823 Panagiotis Kanavos
                //and remove any matching objects from the list, adding them to the commonObjects list
845 c28a075a Panagiotis Kanavos
                
846 c28a075a Panagiotis Kanavos
                if (fileAgent.Exists(relativePath))
847 9c4346c9 Panagiotis Kanavos
                {
848 4f6d51d4 Panagiotis Kanavos
                    //If a directory object already exists, we don't need to perform any other action                    
849 f3d080df Panagiotis Kanavos
                    var localFile = fileAgent.GetFileSystemInfo(relativePath);
850 4f6d51d4 Panagiotis Kanavos
                    if (objectInfo.Content_Type == @"application/directory" && localFile is DirectoryInfo)
851 4f6d51d4 Panagiotis Kanavos
                        continue;
852 e81dd1f6 Panagiotis Kanavos
                    using (new SessionScope(FlushAction.Never))
853 e81dd1f6 Panagiotis Kanavos
                    {
854 039a89e5 Panagiotis Kanavos
                        var state =  StatusKeeper.GetStateByFilePath(localFile.FullName);
855 1a3dfbfd Panagiotis Kanavos
                        _lastSeen[localFile.FullName] = DateTime.Now;
856 039a89e5 Panagiotis Kanavos
                        //FileState.FindByFilePath(localFile.FullName);
857 e81dd1f6 Panagiotis Kanavos
                        //Common files should be checked on a per-case basis to detect differences, which is newer
858 cfed7823 Panagiotis Kanavos
859 e81dd1f6 Panagiotis Kanavos
                        yield return new CloudAction(accountInfo, CloudActionType.MustSynch,
860 e81dd1f6 Panagiotis Kanavos
                                                     localFile, objectInfo, state, accountInfo.BlockSize,
861 e81dd1f6 Panagiotis Kanavos
                                                     accountInfo.BlockHash);
862 e81dd1f6 Panagiotis Kanavos
                    }
863 9c4346c9 Panagiotis Kanavos
                }
864 9c4346c9 Panagiotis Kanavos
                else
865 9c4346c9 Panagiotis Kanavos
                {
866 cfed7823 Panagiotis Kanavos
                    //If there is no match we add them to the localFiles list
867 cfed7823 Panagiotis Kanavos
                    //but only if the file is not marked for deletion
868 c53aa229 Panagiotis Kanavos
                    var targetFile = Path.Combine(accountInfo.AccountPath, relativePath);
869 cfed7823 Panagiotis Kanavos
                    var fileStatus = StatusKeeper.GetFileStatus(targetFile);
870 cfed7823 Panagiotis Kanavos
                    if (fileStatus != FileStatus.Deleted)
871 cfed7823 Panagiotis Kanavos
                    {
872 cfed7823 Panagiotis Kanavos
                        //Remote files should be downloaded
873 c53aa229 Panagiotis Kanavos
                        yield return new CloudDownloadAction(accountInfo,objectInfo);
874 cfed7823 Panagiotis Kanavos
                    }
875 9c4346c9 Panagiotis Kanavos
                }
876 cfed7823 Panagiotis Kanavos
            }            
877 9c4346c9 Panagiotis Kanavos
        }
878 422c9598 Panagiotis Kanavos
*/
879 9c4346c9 Panagiotis Kanavos
880 c28a075a Panagiotis Kanavos
        private static FileAgent GetFileAgent(AccountInfo accountInfo)
881 c28a075a Panagiotis Kanavos
        {
882 c28a075a Panagiotis Kanavos
            return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);
883 c28a075a Panagiotis Kanavos
        }
884 c28a075a Panagiotis Kanavos
885 540b8cf8 Panagiotis Kanavos
        private void ProcessTrashedFiles(AccountInfo accountInfo,IEnumerable<ObjectInfo> trashObjects)
886 0af3141d Panagiotis Kanavos
        {
887 c28a075a Panagiotis Kanavos
            var fileAgent = GetFileAgent(accountInfo);
888 0af3141d Panagiotis Kanavos
            foreach (var trashObject in trashObjects)
889 0af3141d Panagiotis Kanavos
            {
890 692ec33b Panagiotis Kanavos
                var barePath = trashObject.RelativeUrlToFilePath(accountInfo.UserName);
891 692ec33b Panagiotis Kanavos
                //HACK: Assume only the "pithos" container is used. Must find out what happens when
892 692ec33b Panagiotis Kanavos
                //deleting a file from a different container
893 692ec33b Panagiotis Kanavos
                var relativePath = Path.Combine("pithos", barePath);
894 c28a075a Panagiotis Kanavos
                fileAgent.Delete(relativePath);                                
895 0af3141d Panagiotis Kanavos
            }
896 0af3141d Panagiotis Kanavos
        }
897 0af3141d Panagiotis Kanavos
898 9c4346c9 Panagiotis Kanavos
899 27361404 Panagiotis Kanavos
        private void RenameCloudFile(AccountInfo accountInfo,CloudMoveAction action)
900 9c4346c9 Panagiotis Kanavos
        {
901 c53aa229 Panagiotis Kanavos
            if (accountInfo==null)
902 c53aa229 Panagiotis Kanavos
                throw new ArgumentNullException("accountInfo");
903 1bfc38f1 Panagiotis Kanavos
            if (action==null)
904 1bfc38f1 Panagiotis Kanavos
                throw new ArgumentNullException("action");
905 27361404 Panagiotis Kanavos
            if (action.CloudFile==null)
906 27361404 Panagiotis Kanavos
                throw new ArgumentException("CloudFile","action");
907 27361404 Panagiotis Kanavos
            if (action.LocalFile==null)
908 27361404 Panagiotis Kanavos
                throw new ArgumentException("LocalFile","action");
909 27361404 Panagiotis Kanavos
            if (action.OldLocalFile==null)
910 27361404 Panagiotis Kanavos
                throw new ArgumentException("OldLocalFile","action");
911 27361404 Panagiotis Kanavos
            if (action.OldCloudFile==null)
912 27361404 Panagiotis Kanavos
                throw new ArgumentException("OldCloudFile","action");
913 9c4346c9 Panagiotis Kanavos
            Contract.EndContractBlock();
914 27361404 Panagiotis Kanavos
            
915 f3d080df Panagiotis Kanavos
            
916 f3d080df Panagiotis Kanavos
            var newFilePath = action.LocalFile.FullName;
917 f3d080df Panagiotis Kanavos
            
918 f3d080df Panagiotis Kanavos
            //How do we handle concurrent renames and deletes/uploads/downloads?
919 f3d080df Panagiotis Kanavos
            //* A conflicting upload means that a file was renamed before it had a chance to finish uploading
920 f3d080df Panagiotis Kanavos
            //  This should never happen as the network agent executes only one action at a time
921 f3d080df Panagiotis Kanavos
            //* A conflicting download means that the file was modified on the cloud. While we can go on and complete
922 f3d080df Panagiotis Kanavos
            //  the rename, there may be a problem if the file is downloaded in blocks, as subsequent block requests for the 
923 f3d080df Panagiotis Kanavos
            //  same name will fail.
924 f3d080df Panagiotis Kanavos
            //  This should never happen as the network agent executes only one action at a time.
925 f3d080df Panagiotis Kanavos
            //* A conflicting delete can happen if the rename was followed by a delete action that didn't have the chance
926 f3d080df Panagiotis Kanavos
            //  to remove the rename from the queue.
927 f3d080df Panagiotis Kanavos
            //  We can probably ignore this case. It will result in an error which should be ignored            
928 f3d080df Panagiotis Kanavos
929 f3d080df Panagiotis Kanavos
            
930 9c4346c9 Panagiotis Kanavos
            //The local file is already renamed
931 73cdd135 Panagiotis Kanavos
            StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Modified);
932 9c4346c9 Panagiotis Kanavos
933 1bfc38f1 Panagiotis Kanavos
934 1bfc38f1 Panagiotis Kanavos
            var account = action.CloudFile.Account ?? accountInfo.UserName;
935 27361404 Panagiotis Kanavos
            var container = action.CloudFile.Container;
936 1bfc38f1 Panagiotis Kanavos
            
937 c53aa229 Panagiotis Kanavos
            var client = new CloudFilesClient(accountInfo);
938 f3d080df Panagiotis Kanavos
            //TODO: What code is returned when the source file doesn't exist?
939 27361404 Panagiotis Kanavos
            client.MoveObject(account, container, action.OldCloudFile.Name, container, action.CloudFile.Name);
940 9c4346c9 Panagiotis Kanavos
941 73cdd135 Panagiotis Kanavos
            StatusKeeper.SetFileStatus(newFilePath, FileStatus.Unchanged);
942 73cdd135 Panagiotis Kanavos
            StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Normal);
943 27361404 Panagiotis Kanavos
            NativeMethods.RaiseChangeNotification(newFilePath);
944 9c4346c9 Panagiotis Kanavos
        }
945 9c4346c9 Panagiotis Kanavos
946 5e31048f Panagiotis Kanavos
        private void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile)
947 cfed7823 Panagiotis Kanavos
        {
948 c53aa229 Panagiotis Kanavos
            if (accountInfo == null)
949 c53aa229 Panagiotis Kanavos
                throw new ArgumentNullException("accountInfo");
950 1bfc38f1 Panagiotis Kanavos
            if (cloudFile==null)
951 1bfc38f1 Panagiotis Kanavos
                throw new ArgumentNullException("cloudFile");
952 cfed7823 Panagiotis Kanavos
953 ec6f3895 Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(cloudFile.Container))
954 ec6f3895 Panagiotis Kanavos
                throw new ArgumentException("Invalid container", "cloudFile");
955 9c4346c9 Panagiotis Kanavos
            Contract.EndContractBlock();
956 c28a075a Panagiotis Kanavos
            
957 c28a075a Panagiotis Kanavos
            var fileAgent = GetFileAgent(accountInfo);
958 9c4346c9 Panagiotis Kanavos
959 5120f3cb Panagiotis Kanavos
            using ( log4net.ThreadContext.Stacks["DeleteCloudFile"].Push("Delete"))
960 cfed7823 Panagiotis Kanavos
            {
961 5e31048f Panagiotis Kanavos
                var fileName= cloudFile.RelativeUrlToFilePath(accountInfo.UserName);
962 f3d080df Panagiotis Kanavos
                var info = fileAgent.GetFileSystemInfo(fileName);                
963 c53aa229 Panagiotis Kanavos
                var fullPath = info.FullName.ToLower();
964 5e31048f Panagiotis Kanavos
965 73cdd135 Panagiotis Kanavos
                StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified);
966 cfed7823 Panagiotis Kanavos
967 1bfc38f1 Panagiotis Kanavos
                var account = cloudFile.Account ?? accountInfo.UserName;
968 ec6f3895 Panagiotis Kanavos
                var container = cloudFile.Container ;//?? FolderConstants.PithosContainer;
969 1bfc38f1 Panagiotis Kanavos
970 c53aa229 Panagiotis Kanavos
                var client = new CloudFilesClient(accountInfo);
971 5e31048f Panagiotis Kanavos
                client.DeleteObject(account, container, cloudFile.Name);
972 a27aa447 Panagiotis Kanavos
973 73cdd135 Panagiotis Kanavos
                StatusKeeper.ClearFileStatus(fullPath);
974 cfed7823 Panagiotis Kanavos
            }
975 9c4346c9 Panagiotis Kanavos
        }
976 9c4346c9 Panagiotis Kanavos
977 5ce54458 Panagiotis Kanavos
        //Download a file.
978 f3d080df Panagiotis Kanavos
        private async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile , string filePath)
979 9c4346c9 Panagiotis Kanavos
        {
980 c53aa229 Panagiotis Kanavos
            if (accountInfo == null)
981 c53aa229 Panagiotis Kanavos
                throw new ArgumentNullException("accountInfo");
982 d3a13891 Panagiotis Kanavos
            if (cloudFile == null)
983 d3a13891 Panagiotis Kanavos
                throw new ArgumentNullException("cloudFile");
984 1bfc38f1 Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(cloudFile.Account))
985 1bfc38f1 Panagiotis Kanavos
                throw new ArgumentNullException("cloudFile");
986 1bfc38f1 Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(cloudFile.Container))
987 1bfc38f1 Panagiotis Kanavos
                throw new ArgumentNullException("cloudFile");
988 f3d080df Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(filePath))
989 f3d080df Panagiotis Kanavos
                throw new ArgumentNullException("filePath");
990 f3d080df Panagiotis Kanavos
            if (!Path.IsPathRooted(filePath))
991 f3d080df Panagiotis Kanavos
                throw new ArgumentException("The filePath must be rooted", "filePath");
992 0af3141d Panagiotis Kanavos
            Contract.EndContractBlock();
993 eae84ae8 Panagiotis Kanavos
            
994 f3d080df Panagiotis Kanavos
995 f3d080df Panagiotis Kanavos
            var localPath = Interfaces.FileInfoExtensions.GetProperFilePathCapitalization(filePath);
996 73cdd135 Panagiotis Kanavos
            var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
997 d3a13891 Panagiotis Kanavos
998 5ce54458 Panagiotis Kanavos
            var url = relativeUrl.ToString();
999 d3a13891 Panagiotis Kanavos
            if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
1000 27361404 Panagiotis Kanavos
                return;
1001 9c4346c9 Panagiotis Kanavos
1002 039a89e5 Panagiotis Kanavos
1003 5ce54458 Panagiotis Kanavos
            //Are we already downloading or uploading the file? 
1004 5ce54458 Panagiotis Kanavos
            using (var gate=NetworkGate.Acquire(localPath, NetworkOperation.Downloading))
1005 5ce54458 Panagiotis Kanavos
            {
1006 5ce54458 Panagiotis Kanavos
                if (gate.Failed)
1007 27361404 Panagiotis Kanavos
                    return;
1008 5ce54458 Panagiotis Kanavos
                //The file's hashmap will be stored in the same location with the extension .hashmap
1009 77e10b4f Panagiotis Kanavos
                //var hashPath = Path.Combine(FileAgent.CachePath, relativePath + ".hashmap");
1010 5ce54458 Panagiotis Kanavos
                
1011 c53aa229 Panagiotis Kanavos
                var client = new CloudFilesClient(accountInfo);
1012 1bfc38f1 Panagiotis Kanavos
                var account = cloudFile.Account;
1013 1bfc38f1 Panagiotis Kanavos
                var container = cloudFile.Container;
1014 1bfc38f1 Panagiotis Kanavos
1015 73cdd135 Panagiotis Kanavos
                if (cloudFile.Content_Type == @"application/directory")
1016 73cdd135 Panagiotis Kanavos
                {
1017 73cdd135 Panagiotis Kanavos
                    if (!Directory.Exists(localPath))
1018 73cdd135 Panagiotis Kanavos
                        Directory.CreateDirectory(localPath);
1019 73cdd135 Panagiotis Kanavos
                }
1020 27361404 Panagiotis Kanavos
                else
1021 eae84ae8 Panagiotis Kanavos
                {                    
1022 73cdd135 Panagiotis Kanavos
                    //Retrieve the hashmap from the server
1023 73cdd135 Panagiotis Kanavos
                    var serverHash = await client.GetHashMap(account, container, url);
1024 73cdd135 Panagiotis Kanavos
                    //If it's a small file
1025 73cdd135 Panagiotis Kanavos
                    if (serverHash.Hashes.Count == 1)
1026 73cdd135 Panagiotis Kanavos
                        //Download it in one go
1027 73cdd135 Panagiotis Kanavos
                        await
1028 73cdd135 Panagiotis Kanavos
                            DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash);
1029 73cdd135 Panagiotis Kanavos
                        //Otherwise download it block by block
1030 73cdd135 Panagiotis Kanavos
                    else
1031 73cdd135 Panagiotis Kanavos
                        await DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash);
1032 73cdd135 Panagiotis Kanavos
1033 73cdd135 Panagiotis Kanavos
                    if (cloudFile.AllowedTo == "read")
1034 73cdd135 Panagiotis Kanavos
                    {
1035 73cdd135 Panagiotis Kanavos
                        var attributes = File.GetAttributes(localPath);
1036 73cdd135 Panagiotis Kanavos
                        File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);                        
1037 73cdd135 Panagiotis Kanavos
                    }
1038 d3a13891 Panagiotis Kanavos
                }
1039 73cdd135 Panagiotis Kanavos
1040 1bfc38f1 Panagiotis Kanavos
                //Now we can store the object's metadata without worrying about ghost status entries
1041 1bfc38f1 Panagiotis Kanavos
                StatusKeeper.StoreInfo(localPath, cloudFile);
1042 0af3141d Panagiotis Kanavos
                
1043 5ce54458 Panagiotis Kanavos
            }
1044 9c4346c9 Panagiotis Kanavos
        }
1045 9c4346c9 Panagiotis Kanavos
1046 a27aa447 Panagiotis Kanavos
        //Download a small file with a single GET operation
1047 f3d080df Panagiotis Kanavos
        private async Task DownloadEntireFileAsync(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath,TreeHash serverHash)
1048 a27aa447 Panagiotis Kanavos
        {
1049 c53aa229 Panagiotis Kanavos
            if (client == null)
1050 c53aa229 Panagiotis Kanavos
                throw new ArgumentNullException("client");
1051 1bfc38f1 Panagiotis Kanavos
            if (cloudFile==null)
1052 1bfc38f1 Panagiotis Kanavos
                throw new ArgumentNullException("cloudFile");
1053 a27aa447 Panagiotis Kanavos
            if (relativeUrl == null)
1054 a27aa447 Panagiotis Kanavos
                throw new ArgumentNullException("relativeUrl");
1055 f3d080df Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(filePath))
1056 f3d080df Panagiotis Kanavos
                throw new ArgumentNullException("filePath");
1057 f3d080df Panagiotis Kanavos
            if (!Path.IsPathRooted(filePath))
1058 f3d080df Panagiotis Kanavos
                throw new ArgumentException("The localPath must be rooted", "filePath");
1059 a27aa447 Panagiotis Kanavos
            Contract.EndContractBlock();
1060 a27aa447 Panagiotis Kanavos
1061 f3d080df Panagiotis Kanavos
            var localPath = Pithos.Interfaces.FileInfoExtensions.GetProperFilePathCapitalization(filePath);
1062 d3a13891 Panagiotis Kanavos
            //If the file already exists
1063 d3a13891 Panagiotis Kanavos
            if (File.Exists(localPath))
1064 d3a13891 Panagiotis Kanavos
            {
1065 d3a13891 Panagiotis Kanavos
                //First check with MD5 as this is a small file
1066 d3a13891 Panagiotis Kanavos
                var localMD5 = Signature.CalculateMD5(localPath);
1067 d3a13891 Panagiotis Kanavos
                var cloudHash=serverHash.TopHash.ToHashString();
1068 d3a13891 Panagiotis Kanavos
                if (localMD5==cloudHash)
1069 27361404 Panagiotis Kanavos
                    return;
1070 d3a13891 Panagiotis Kanavos
                //Then check with a treehash
1071 d3a13891 Panagiotis Kanavos
                var localTreeHash = Signature.CalculateTreeHash(localPath, serverHash.BlockSize, serverHash.BlockHash);
1072 d3a13891 Panagiotis Kanavos
                var localHash = localTreeHash.TopHash.ToHashString();
1073 d3a13891 Panagiotis Kanavos
                if (localHash==cloudHash)
1074 27361404 Panagiotis Kanavos
                    return;
1075 d3a13891 Panagiotis Kanavos
            }
1076 eae84ae8 Panagiotis Kanavos
            StatusNotification.Notify(new CloudNotification { Data = cloudFile });
1077 d3a13891 Panagiotis Kanavos
1078 c28a075a Panagiotis Kanavos
            var fileAgent = GetFileAgent(accountInfo);
1079 a27aa447 Panagiotis Kanavos
            //Calculate the relative file path for the new file
1080 a27aa447 Panagiotis Kanavos
            var relativePath = relativeUrl.RelativeUriToFilePath();
1081 a27aa447 Panagiotis Kanavos
            //The file will be stored in a temporary location while downloading with an extension .download
1082 77e10b4f Panagiotis Kanavos
            var tempPath = Path.Combine(fileAgent.CachePath, relativePath + ".download");
1083 a27aa447 Panagiotis Kanavos
            //Make sure the target folder exists. DownloadFileTask will not create the folder
1084 d3a13891 Panagiotis Kanavos
            var tempFolder = Path.GetDirectoryName(tempPath);
1085 d3a13891 Panagiotis Kanavos
            if (!Directory.Exists(tempFolder))
1086 d3a13891 Panagiotis Kanavos
                Directory.CreateDirectory(tempFolder);
1087 a27aa447 Panagiotis Kanavos
1088 a27aa447 Panagiotis Kanavos
            //Download the object to the temporary location
1089 f3d080df Panagiotis Kanavos
            await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath);
1090 0bd56b7c Panagiotis Kanavos
1091 f3d080df Panagiotis Kanavos
            //Create the local folder if it doesn't exist (necessary for shared objects)
1092 f3d080df Panagiotis Kanavos
            var localFolder = Path.GetDirectoryName(localPath);
1093 f3d080df Panagiotis Kanavos
            if (!Directory.Exists(localFolder))
1094 f3d080df Panagiotis Kanavos
                Directory.CreateDirectory(localFolder);            
1095 f3d080df Panagiotis Kanavos
            //And move it to its actual location once downloading is finished
1096 f3d080df Panagiotis Kanavos
            if (File.Exists(localPath))
1097 f3d080df Panagiotis Kanavos
                File.Replace(tempPath,localPath,null,true);
1098 f3d080df Panagiotis Kanavos
            else
1099 f3d080df Panagiotis Kanavos
                File.Move(tempPath,localPath);
1100 f3d080df Panagiotis Kanavos
            //Notify listeners that a local file has changed
1101 f3d080df Panagiotis Kanavos
            StatusNotification.NotifyChangedFile(localPath);
1102 f3d080df Panagiotis Kanavos
1103 f3d080df Panagiotis Kanavos
                       
1104 a27aa447 Panagiotis Kanavos
        }
1105 a27aa447 Panagiotis Kanavos
1106 0af3141d Panagiotis Kanavos
        //Download a file asynchronously using blocks
1107 f3d080df Panagiotis Kanavos
        public async Task DownloadWithBlocks(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, TreeHash serverHash)
1108 0af3141d Panagiotis Kanavos
        {
1109 c53aa229 Panagiotis Kanavos
            if (client == null)
1110 c53aa229 Panagiotis Kanavos
                throw new ArgumentNullException("client");
1111 1bfc38f1 Panagiotis Kanavos
            if (cloudFile == null)
1112 1bfc38f1 Panagiotis Kanavos
                throw new ArgumentNullException("cloudFile");
1113 0af3141d Panagiotis Kanavos
            if (relativeUrl == null)
1114 0af3141d Panagiotis Kanavos
                throw new ArgumentNullException("relativeUrl");
1115 f3d080df Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(filePath))
1116 f3d080df Panagiotis Kanavos
                throw new ArgumentNullException("filePath");
1117 f3d080df Panagiotis Kanavos
            if (!Path.IsPathRooted(filePath))
1118 f3d080df Panagiotis Kanavos
                throw new ArgumentException("The filePath must be rooted", "filePath");
1119 0af3141d Panagiotis Kanavos
            if (serverHash == null)
1120 0af3141d Panagiotis Kanavos
                throw new ArgumentNullException("serverHash");
1121 0af3141d Panagiotis Kanavos
            Contract.EndContractBlock();
1122 0af3141d Panagiotis Kanavos
            
1123 27361404 Panagiotis Kanavos
           var fileAgent = GetFileAgent(accountInfo);
1124 f3d080df Panagiotis Kanavos
            var localPath = Interfaces.FileInfoExtensions.GetProperFilePathCapitalization(filePath);
1125 a64c87c8 Panagiotis Kanavos
            
1126 a27aa447 Panagiotis Kanavos
            //Calculate the relative file path for the new file
1127 a27aa447 Panagiotis Kanavos
            var relativePath = relativeUrl.RelativeUriToFilePath();
1128 77e10b4f Panagiotis Kanavos
            var blockUpdater = new BlockUpdater(fileAgent.CachePath, localPath, relativePath, serverHash);
1129 a27aa447 Panagiotis Kanavos
1130 a64c87c8 Panagiotis Kanavos
            
1131 0af3141d Panagiotis Kanavos
                        
1132 0af3141d Panagiotis Kanavos
            //Calculate the file's treehash
1133 422c9598 Panagiotis Kanavos
            var treeHash = await Signature.CalculateTreeHashAsync(localPath, serverHash.BlockSize, serverHash.BlockHash, 2);
1134 a27aa447 Panagiotis Kanavos
                
1135 0af3141d Panagiotis Kanavos
            //And compare it with the server's hash
1136 0af3141d Panagiotis Kanavos
            var upHashes = serverHash.GetHashesAsStrings();
1137 0af3141d Panagiotis Kanavos
            var localHashes = treeHash.HashDictionary;
1138 0af3141d Panagiotis Kanavos
            for (int i = 0; i < upHashes.Length; i++)
1139 0af3141d Panagiotis Kanavos
            {
1140 0af3141d Panagiotis Kanavos
                //For every non-matching hash
1141 0af3141d Panagiotis Kanavos
                var upHash = upHashes[i];
1142 0af3141d Panagiotis Kanavos
                if (!localHashes.ContainsKey(upHash))
1143 a27aa447 Panagiotis Kanavos
                {
1144 eae84ae8 Panagiotis Kanavos
                    StatusNotification.Notify(new CloudNotification { Data = cloudFile });
1145 eae84ae8 Panagiotis Kanavos
1146 0af3141d Panagiotis Kanavos
                    if (blockUpdater.UseOrphan(i, upHash))
1147 a27aa447 Panagiotis Kanavos
                    {
1148 cfed7823 Panagiotis Kanavos
                        Log.InfoFormat("[BLOCK GET] ORPHAN FOUND for {0} of {1} for {2}", i, upHashes.Length, localPath);
1149 0af3141d Panagiotis Kanavos
                        continue;
1150 0af3141d Panagiotis Kanavos
                    }
1151 cfed7823 Panagiotis Kanavos
                    Log.InfoFormat("[BLOCK GET] START {0} of {1} for {2}", i, upHashes.Length, localPath);
1152 c53aa229 Panagiotis Kanavos
                    var start = i*serverHash.BlockSize;
1153 0af3141d Panagiotis Kanavos
                    //To download the last block just pass a null for the end of the range
1154 0af3141d Panagiotis Kanavos
                    long? end = null;
1155 0af3141d Panagiotis Kanavos
                    if (i < upHashes.Length - 1 )
1156 c53aa229 Panagiotis Kanavos
                        end= ((i + 1)*serverHash.BlockSize) ;
1157 a27aa447 Panagiotis Kanavos
                            
1158 0af3141d Panagiotis Kanavos
                    //Download the missing block
1159 27361404 Panagiotis Kanavos
                    var block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end);
1160 a64c87c8 Panagiotis Kanavos
1161 0af3141d Panagiotis Kanavos
                    //and store it
1162 27361404 Panagiotis Kanavos
                    blockUpdater.StoreBlock(i, block);
1163 a27aa447 Panagiotis Kanavos
1164 cfed7823 Panagiotis Kanavos
1165 cfed7823 Panagiotis Kanavos
                    Log.InfoFormat("[BLOCK GET] FINISH {0} of {1} for {2}", i, upHashes.Length, localPath);
1166 a27aa447 Panagiotis Kanavos
                }
1167 0af3141d Panagiotis Kanavos
            }
1168 a27aa447 Panagiotis Kanavos
1169 0bd56b7c Panagiotis Kanavos
            //Want to avoid notifications if no changes were made
1170 0bd56b7c Panagiotis Kanavos
            var hasChanges = blockUpdater.HasBlocks;
1171 0af3141d Panagiotis Kanavos
            blockUpdater.Commit();
1172 0bd56b7c Panagiotis Kanavos
            
1173 0bd56b7c Panagiotis Kanavos
            if (hasChanges)
1174 0bd56b7c Panagiotis Kanavos
                //Notify listeners that a local file has changed
1175 0bd56b7c Panagiotis Kanavos
                StatusNotification.NotifyChangedFile(localPath);
1176 0bd56b7c Panagiotis Kanavos
1177 cfed7823 Panagiotis Kanavos
            Log.InfoFormat("[BLOCK GET] COMPLETE {0}", localPath);            
1178 0af3141d Panagiotis Kanavos
        }
1179 a27aa447 Panagiotis Kanavos
1180 a27aa447 Panagiotis Kanavos
1181 27361404 Panagiotis Kanavos
        private async Task UploadCloudFile(CloudAction action)
1182 9c4346c9 Panagiotis Kanavos
        {
1183 1bfc38f1 Panagiotis Kanavos
            if (action == null)
1184 1bfc38f1 Panagiotis Kanavos
                throw new ArgumentNullException("action");           
1185 0af3141d Panagiotis Kanavos
            Contract.EndContractBlock();
1186 0af3141d Panagiotis Kanavos
1187 1bfc38f1 Panagiotis Kanavos
            try
1188 eae84ae8 Panagiotis Kanavos
            {                
1189 692ec33b Panagiotis Kanavos
                var accountInfo = action.AccountInfo;
1190 692ec33b Panagiotis Kanavos
1191 692ec33b Panagiotis Kanavos
                var fileInfo = action.LocalFile;
1192 1bfc38f1 Panagiotis Kanavos
1193 437abfca Panagiotis Kanavos
                if (fileInfo.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase))
1194 437abfca Panagiotis Kanavos
                    return;
1195 039a89e5 Panagiotis Kanavos
                
1196 437abfca Panagiotis Kanavos
                var relativePath = fileInfo.AsRelativeTo(accountInfo.AccountPath);
1197 437abfca Panagiotis Kanavos
                if (relativePath.StartsWith(FolderConstants.OthersFolder))
1198 1bfc38f1 Panagiotis Kanavos
                {
1199 692ec33b Panagiotis Kanavos
                    var parts = relativePath.Split('\\');
1200 437abfca Panagiotis Kanavos
                    var accountName = parts[1];
1201 437abfca Panagiotis Kanavos
                    var oldName = accountInfo.UserName;
1202 437abfca Panagiotis Kanavos
                    var absoluteUri = accountInfo.StorageUri.AbsoluteUri;
1203 692ec33b Panagiotis Kanavos
                    var nameIndex = absoluteUri.IndexOf(oldName);
1204 692ec33b Panagiotis Kanavos
                    var root = absoluteUri.Substring(0, nameIndex);
1205 437abfca Panagiotis Kanavos
1206 437abfca Panagiotis Kanavos
                    accountInfo = new AccountInfo
1207 437abfca Panagiotis Kanavos
                    {
1208 437abfca Panagiotis Kanavos
                        UserName = accountName,
1209 437abfca Panagiotis Kanavos
                        AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
1210 437abfca Panagiotis Kanavos
                        StorageUri = new Uri(root + accountName),
1211 692ec33b Panagiotis Kanavos
                        BlockHash = accountInfo.BlockHash,
1212 692ec33b Panagiotis Kanavos
                        BlockSize = accountInfo.BlockSize,
1213 692ec33b Panagiotis Kanavos
                        Token = accountInfo.Token
1214 437abfca Panagiotis Kanavos
                    };
1215 437abfca Panagiotis Kanavos
                }
1216 1bfc38f1 Panagiotis Kanavos
1217 1bfc38f1 Panagiotis Kanavos
1218 f3d080df Panagiotis Kanavos
                var fullFileName = fileInfo.GetProperCapitalization();
1219 692ec33b Panagiotis Kanavos
                using (var gate = NetworkGate.Acquire(fullFileName, NetworkOperation.Uploading))
1220 437abfca Panagiotis Kanavos
                {
1221 437abfca Panagiotis Kanavos
                    //Abort if the file is already being uploaded or downloaded
1222 437abfca Panagiotis Kanavos
                    if (gate.Failed)
1223 437abfca Panagiotis Kanavos
                        return;
1224 9c4346c9 Panagiotis Kanavos
1225 437abfca Panagiotis Kanavos
                    var cloudFile = action.CloudFile;
1226 437abfca Panagiotis Kanavos
                    var account = cloudFile.Account ?? accountInfo.UserName;
1227 1bfc38f1 Panagiotis Kanavos
1228 4f6d51d4 Panagiotis Kanavos
                    var client = new CloudFilesClient(accountInfo);                    
1229 437abfca Panagiotis Kanavos
                    //Even if GetObjectInfo times out, we can proceed with the upload            
1230 692ec33b Panagiotis Kanavos
                    var info = client.GetObjectInfo(account, cloudFile.Container, cloudFile.Name);
1231 5ce54458 Panagiotis Kanavos
1232 4f6d51d4 Panagiotis Kanavos
                    //If this is a read-only file, do not upload changes
1233 4f6d51d4 Panagiotis Kanavos
                    if (info.AllowedTo == "read")
1234 4f6d51d4 Panagiotis Kanavos
                        return;
1235 f3d080df Panagiotis Kanavos
                    
1236 f3d080df Panagiotis Kanavos
                    //TODO: Check how a directory hash is calculated -> All dirs seem to have the same hash
1237 4f6d51d4 Panagiotis Kanavos
                    if (fileInfo is DirectoryInfo)
1238 437abfca Panagiotis Kanavos
                    {
1239 4f6d51d4 Panagiotis Kanavos
                        //If the directory doesn't exist the Hash property will be empty
1240 4f6d51d4 Panagiotis Kanavos
                        if (String.IsNullOrWhiteSpace(info.Hash))
1241 4f6d51d4 Panagiotis Kanavos
                            //Go on and create the directory
1242 3c76f045 Panagiotis Kanavos
                            await client.PutObject(account, cloudFile.Container, cloudFile.Name, fullFileName, String.Empty, "application/directory");
1243 437abfca Panagiotis Kanavos
                    }
1244 4f6d51d4 Panagiotis Kanavos
                    else
1245 4f6d51d4 Panagiotis Kanavos
                    {
1246 5ce54458 Panagiotis Kanavos
1247 4f6d51d4 Panagiotis Kanavos
                        var cloudHash = info.Hash.ToLower();
1248 d3a13891 Panagiotis Kanavos
1249 4f6d51d4 Panagiotis Kanavos
                        var hash = action.LocalHash.Value;
1250 4f6d51d4 Panagiotis Kanavos
                        var topHash = action.TopHash.Value;
1251 4f6d51d4 Panagiotis Kanavos
1252 4f6d51d4 Panagiotis Kanavos
                        //If the file hashes match, abort the upload
1253 4f6d51d4 Panagiotis Kanavos
                        if (hash == cloudHash || topHash == cloudHash)
1254 4f6d51d4 Panagiotis Kanavos
                        {
1255 4f6d51d4 Panagiotis Kanavos
                            //but store any metadata changes 
1256 4f6d51d4 Panagiotis Kanavos
                            StatusKeeper.StoreInfo(fullFileName, info);
1257 4f6d51d4 Panagiotis Kanavos
                            Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName);
1258 4f6d51d4 Panagiotis Kanavos
                            return;
1259 4f6d51d4 Panagiotis Kanavos
                        }
1260 a27aa447 Panagiotis Kanavos
1261 5750d7cc Panagiotis Kanavos
1262 4f6d51d4 Panagiotis Kanavos
                        //Mark the file as modified while we upload it
1263 4f6d51d4 Panagiotis Kanavos
                        StatusKeeper.SetFileOverlayStatus(fullFileName, FileOverlayStatus.Modified);
1264 4f6d51d4 Panagiotis Kanavos
                        //And then upload it
1265 692ec33b Panagiotis Kanavos
1266 4f6d51d4 Panagiotis Kanavos
                        //Upload even small files using the Hashmap. The server may already contain
1267 4f6d51d4 Panagiotis Kanavos
                        //the relevant block
1268 5750d7cc Panagiotis Kanavos
1269 4f6d51d4 Panagiotis Kanavos
                        //First, calculate the tree hash
1270 f3d080df Panagiotis Kanavos
                        var treeHash = await Signature.CalculateTreeHashAsync(fullFileName, accountInfo.BlockSize,
1271 422c9598 Panagiotis Kanavos
                                                                              accountInfo.BlockHash, 2);
1272 4f6d51d4 Panagiotis Kanavos
1273 4f6d51d4 Panagiotis Kanavos
                        await UploadWithHashMap(accountInfo, cloudFile, fileInfo as FileInfo, cloudFile.Name, treeHash);
1274 4f6d51d4 Panagiotis Kanavos
                    }
1275 437abfca Panagiotis Kanavos
                    //If everything succeeds, change the file and overlay status to normal
1276 73cdd135 Panagiotis Kanavos
                    StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged, FileOverlayStatus.Normal);
1277 437abfca Panagiotis Kanavos
                }
1278 437abfca Panagiotis Kanavos
                //Notify the Shell to update the overlays
1279 437abfca Panagiotis Kanavos
                NativeMethods.RaiseChangeNotification(fullFileName);
1280 437abfca Panagiotis Kanavos
                StatusNotification.NotifyChangedFile(fullFileName);
1281 5ce54458 Panagiotis Kanavos
            }
1282 437abfca Panagiotis Kanavos
            catch (AggregateException ex)
1283 437abfca Panagiotis Kanavos
            {
1284 437abfca Panagiotis Kanavos
                var exc = ex.InnerException as WebException;
1285 437abfca Panagiotis Kanavos
                if (exc == null)
1286 437abfca Panagiotis Kanavos
                    throw ex.InnerException;
1287 692ec33b Panagiotis Kanavos
                if (HandleUploadWebException(action, exc)) 
1288 437abfca Panagiotis Kanavos
                    return;
1289 692ec33b Panagiotis Kanavos
                throw;
1290 692ec33b Panagiotis Kanavos
            }
1291 692ec33b Panagiotis Kanavos
            catch (WebException ex)
1292 692ec33b Panagiotis Kanavos
            {
1293 692ec33b Panagiotis Kanavos
                if (HandleUploadWebException(action, ex))
1294 692ec33b Panagiotis Kanavos
                    return;
1295 692ec33b Panagiotis Kanavos
                throw;
1296 692ec33b Panagiotis Kanavos
            }
1297 692ec33b Panagiotis Kanavos
            catch (Exception ex)
1298 692ec33b Panagiotis Kanavos
            {
1299 692ec33b Panagiotis Kanavos
                Log.Error("Unexpected error while uploading file", ex);
1300 437abfca Panagiotis Kanavos
                throw;
1301 437abfca Panagiotis Kanavos
            }
1302 437abfca Panagiotis Kanavos
1303 9c4346c9 Panagiotis Kanavos
        }
1304 9c4346c9 Panagiotis Kanavos
1305 2edb4807 Panagiotis Kanavos
        //Returns true if an action concerns a file that was deleted
1306 039a89e5 Panagiotis Kanavos
        private bool IsDeletedFile(CloudAction action)
1307 2edb4807 Panagiotis Kanavos
        {
1308 2edb4807 Panagiotis Kanavos
            //Doesn't work for actions targeting shared files
1309 2edb4807 Panagiotis Kanavos
            if (action.IsShared)
1310 2edb4807 Panagiotis Kanavos
                return false;
1311 039a89e5 Panagiotis Kanavos
            var key = GetFileKey(action.CloudFile);
1312 039a89e5 Panagiotis Kanavos
            DateTime entryDate;
1313 039a89e5 Panagiotis Kanavos
            if (_deletedFiles.TryGetValue(key, out entryDate))
1314 039a89e5 Panagiotis Kanavos
            {
1315 039a89e5 Panagiotis Kanavos
                //If the delete entry was created after this action, abort the action
1316 039a89e5 Panagiotis Kanavos
                if (entryDate > action.Created)
1317 039a89e5 Panagiotis Kanavos
                    return true;
1318 039a89e5 Panagiotis Kanavos
                //Otherwise, remove the stale entry 
1319 039a89e5 Panagiotis Kanavos
                _deletedFiles.TryRemove(key, out entryDate);
1320 039a89e5 Panagiotis Kanavos
            }
1321 039a89e5 Panagiotis Kanavos
            return false;
1322 039a89e5 Panagiotis Kanavos
        }
1323 039a89e5 Panagiotis Kanavos
1324 692ec33b Panagiotis Kanavos
        private bool HandleUploadWebException(CloudAction action, WebException exc)
1325 692ec33b Panagiotis Kanavos
        {
1326 692ec33b Panagiotis Kanavos
            var response = exc.Response as HttpWebResponse;
1327 692ec33b Panagiotis Kanavos
            if (response == null)
1328 692ec33b Panagiotis Kanavos
                throw exc;
1329 692ec33b Panagiotis Kanavos
            if (response.StatusCode == HttpStatusCode.Unauthorized)
1330 692ec33b Panagiotis Kanavos
            {
1331 692ec33b Panagiotis Kanavos
                Log.Error("Not allowed to upload file", exc);
1332 692ec33b Panagiotis Kanavos
                var message = String.Format("Not allowed to uplad file {0}", action.LocalFile.FullName);
1333 692ec33b Panagiotis Kanavos
                StatusKeeper.SetFileState(action.LocalFile.FullName, FileStatus.Unchanged, FileOverlayStatus.Normal);
1334 692ec33b Panagiotis Kanavos
                StatusNotification.NotifyChange(message, TraceLevel.Warning);
1335 692ec33b Panagiotis Kanavos
                return true;
1336 692ec33b Panagiotis Kanavos
            }
1337 692ec33b Panagiotis Kanavos
            return false;
1338 692ec33b Panagiotis Kanavos
        }
1339 692ec33b Panagiotis Kanavos
1340 27361404 Panagiotis Kanavos
        public async Task UploadWithHashMap(AccountInfo accountInfo,ObjectInfo cloudFile,FileInfo fileInfo,string url,TreeHash treeHash)
1341 a27aa447 Panagiotis Kanavos
        {
1342 c53aa229 Panagiotis Kanavos
            if (accountInfo == null)
1343 c53aa229 Panagiotis Kanavos
                throw new ArgumentNullException("accountInfo");
1344 1bfc38f1 Panagiotis Kanavos
            if (cloudFile==null)
1345 1bfc38f1 Panagiotis Kanavos
                throw new ArgumentNullException("cloudFile");
1346 cfed7823 Panagiotis Kanavos
            if (fileInfo == null)
1347 0af3141d Panagiotis Kanavos
                throw new ArgumentNullException("fileInfo");
1348 0af3141d Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(url))
1349 0af3141d Panagiotis Kanavos
                throw new ArgumentNullException(url);
1350 0af3141d Panagiotis Kanavos
            if (treeHash==null)
1351 0af3141d Panagiotis Kanavos
                throw new ArgumentNullException("treeHash");
1352 ec6f3895 Panagiotis Kanavos
            if (String.IsNullOrWhiteSpace(cloudFile.Container) )
1353 ec6f3895 Panagiotis Kanavos
                throw new ArgumentException("Invalid container","cloudFile");
1354 0af3141d Panagiotis Kanavos
            Contract.EndContractBlock();
1355 0af3141d Panagiotis Kanavos
1356 f3d080df Panagiotis Kanavos
            var fullFileName = fileInfo.GetProperCapitalization();
1357 a27aa447 Panagiotis Kanavos
1358 1bfc38f1 Panagiotis Kanavos
            var account = cloudFile.Account ?? accountInfo.UserName;
1359 27361404 Panagiotis Kanavos
            var container = cloudFile.Container ;
1360 1bfc38f1 Panagiotis Kanavos
1361 c53aa229 Panagiotis Kanavos
            var client = new CloudFilesClient(accountInfo);
1362 a27aa447 Panagiotis Kanavos
            //Send the hashmap to the server            
1363 437abfca Panagiotis Kanavos
            var missingHashes =  await client.PutHashMap(account, container, url, treeHash);
1364 0af3141d Panagiotis Kanavos
            //If the server returns no missing hashes, we are done
1365 0af3141d Panagiotis Kanavos
            while (missingHashes.Count > 0)
1366 a27aa447 Panagiotis Kanavos
            {
1367 0af3141d Panagiotis Kanavos
1368 c53aa229 Panagiotis Kanavos
                var buffer = new byte[accountInfo.BlockSize];
1369 0af3141d Panagiotis Kanavos
                foreach (var missingHash in missingHashes)
1370 a27aa447 Panagiotis Kanavos
                {
1371 a27aa447 Panagiotis Kanavos
                    //Find the proper block
1372 437abfca Panagiotis Kanavos
                    var blockIndex = treeHash.HashDictionary[missingHash];
1373 c53aa229 Panagiotis Kanavos
                    var offset = blockIndex*accountInfo.BlockSize;
1374 a27aa447 Panagiotis Kanavos
1375 c53aa229 Panagiotis Kanavos
                    var read = fileInfo.Read(buffer, offset, accountInfo.BlockSize);
1376 0af3141d Panagiotis Kanavos
1377 437abfca Panagiotis Kanavos
                    try
1378 437abfca Panagiotis Kanavos
                    {
1379 437abfca Panagiotis Kanavos
                        //And upload the block                
1380 437abfca Panagiotis Kanavos
                        await client.PostBlock(account, container, buffer, 0, read);
1381 437abfca Panagiotis Kanavos
                        Log.InfoFormat("[BLOCK] Block {0} of {1} uploaded", blockIndex, fullFileName);
1382 437abfca Panagiotis Kanavos
                    }
1383 437abfca Panagiotis Kanavos
                    catch (Exception exc)
1384 437abfca Panagiotis Kanavos
                    {
1385 437abfca Panagiotis Kanavos
                        Log.ErrorFormat("[ERROR] uploading block {0} of {1}\n{2}", blockIndex, fullFileName, exc);
1386 437abfca Panagiotis Kanavos
                    }
1387 0af3141d Panagiotis Kanavos
1388 a27aa447 Panagiotis Kanavos
                }
1389 a27aa447 Panagiotis Kanavos
1390 437abfca Panagiotis Kanavos
                //Repeat until there are no more missing hashes                
1391 437abfca Panagiotis Kanavos
                missingHashes = await client.PutHashMap(account, container, url, treeHash);
1392 0af3141d Panagiotis Kanavos
            }
1393 a27aa447 Panagiotis Kanavos
        }
1394 a27aa447 Panagiotis Kanavos
1395 9c4346c9 Panagiotis Kanavos
1396 c53aa229 Panagiotis Kanavos
        public void AddAccount(AccountInfo accountInfo)
1397 c53aa229 Panagiotis Kanavos
        {            
1398 c53aa229 Panagiotis Kanavos
            if (!_accounts.Contains(accountInfo))
1399 c53aa229 Panagiotis Kanavos
                _accounts.Add(accountInfo);
1400 c53aa229 Panagiotis Kanavos
        }
1401 9c4346c9 Panagiotis Kanavos
    }
1402 9c4346c9 Panagiotis Kanavos
1403 5ce54458 Panagiotis Kanavos
   
1404 5ce54458 Panagiotis Kanavos
1405 9c4346c9 Panagiotis Kanavos
1406 9c4346c9 Panagiotis Kanavos
}