Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / NetworkAgent.cs @ 025046f1

History | View | Annotate | Download (58.8 kB)

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