Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / NetworkAgent.cs @ 34bdb91d

History | View | Annotate | Download (52.1 kB)

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