Revision ec1a1baf

b/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs
738 738
			var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName);
739 739
			_accounts.TryRemove(accountInfo);
740 740

  
741
		    _pollAgent.RemoveAccount(accountInfo);
741 742
			PithosMonitor monitor;
742 743
			if (Monitors.TryRemove(accountName, out monitor))
743 744
			{
b/trunk/Pithos.Core/Agents/Agent.cs
43 43
using System.Collections.Concurrent;
44 44
using System.Collections.Generic;
45 45
using System.Diagnostics.Contracts;
46
using System.Linq;
47
using System.Text;
48 46
using System.Threading;
47
using System.Threading.Async;
49 48
using System.Threading.Tasks;
50 49
using Pithos.Core.Agents;
51 50

  
......
54 53
    public class Agent<TMessage> : IDisposable
55 54
    {
56 55
        private readonly ConcurrentQueue<TMessage> _queue;
57
        private readonly BlockingCollection<TMessage> _messages;
56
        private readonly AsyncProducerConsumerCollection<TMessage> _messages;
58 57
        private readonly CancellationTokenSource _cancelSource = new CancellationTokenSource();
59 58
        public CancellationToken CancellationToken;
60 59

  
......
64 63
        public Agent(Action<Agent<TMessage>> action)
65 64
        {
66 65
            _queue=new ConcurrentQueue<TMessage>();
67
            _messages = new BlockingCollection<TMessage>(_queue);
66
            _messages = new AsyncProducerConsumerCollection<TMessage>(_queue);            
68 67
            _process = action;
69 68
            CancellationToken = _cancelSource.Token;
70 69
        }
......
82 81
        /// <summary>
83 82
        /// Receives a message asynchronously, optionally with a timeout. Receive throws a TimeoutException if the timeout expires
84 83
        /// </summary>
85
        /// <param name="timeout">Optional timeout in milliseconds. If provided, Receive fails with a TimeoutException if no message is available in the specified time</param>
86 84
        /// <returns>A Task that will return the message asynchronously</returns>
87
        public Task<TMessage> Receive(int timeout = -1)
85
        public  Task<TMessage> Receive()
88 86
        {
89
            return Task<TMessage>.Factory.StartNew(() =>
90
            {
91
                TMessage item;
92
                if (!_messages.TryTake(out item, timeout, CancellationToken))
93
                    throw new TimeoutException();
94
                return item;
95
            });
96
        }
97

  
98

  
99
        /// <summary>
100
        /// Receives a message asynchronously, optionally with a timeout. TryReceive returns an empty task if the timeout expires
101
        /// </summary>
102
        /// <param name="timeout">Optional timeout in milliseconds. If provided, Receive returns an empty task</param>
103
        /// <returns>A Task that will return the message asynchronously</returns>
104
        public Task<TMessage> TryReceive(int timeout = -1)
105
        {
106
            return Task<TMessage>.Factory.StartNew(() =>
107
            {
108
                TMessage item;
109
                _messages.TryTake(out item, timeout, CancellationToken);
110
                return item;
111
            });
87
            return _messages.Take();
112 88
        }
113 89

  
114 90

  
......
140 116
        public void Stop()
141 117
        {
142 118
            //Stop the message queue
143
            _messages.CompleteAdding();
119
            //_messages.CompleteAdding();
144 120
            //And signal the cancellation
145 121
            _cancelSource.Cancel();
146 122
        }
......
179 155

  
180 156
        public IEnumerable<TMessage> GetEnumerable()
181 157
        {
182
            return _messages;
158
            return _queue;
183 159
        }
184 160

  
185 161
        /// <summary>
b/trunk/Pithos.Core/Agents/CloudTransferAction.cs
72 72

  
73 73

  
74 74
        public Lazy<string> LocalHash { get; protected set; }
75
        private Lazy<string> _topHash;
76
        public Lazy<string> TopHash
77
        {
78
            get { return _topHash; }
79
            set { _topHash = value; }
80
        }
75
        public Lazy<string> TopHash { get; set; }
81 76

  
82 77

  
83 78
        [ContractInvariantMethod]
......
126 121

  
127 122
        public override string ToString()
128 123
        {
129
            return String.Format("{0}:{1}->{2}", this.Action, this.LocalFile.FullName, this.CloudFile.Name);
124
            return String.Format("{0}:{1}->{2}", Action, LocalFile.FullName, CloudFile.Name);
130 125
        }
131 126

  
132 127
        protected static ObjectInfo CreateObjectInfoFor(AccountInfo accountInfo, FileSystemInfo fileInfo)
......
208 203

  
209 204
        public override string ToString()
210 205
        {
211
            return String.Format("{0}: _ ->{1}", this.Action, this.CloudFile.Name);
206
            return String.Format("{0}: _ ->{1}", Action, CloudFile.Name);
212 207
        }
213 208

  
214 209
    }
......
249 244

  
250 245
        public override string ToString()
251 246
        {
252
            return String.Format("{0}:{1}->{2}", this.Action, OldCloudFile.Name, CloudFile.Name);
247
            return String.Format("{0}:{1}->{2}", Action, OldCloudFile.Name, CloudFile.Name);
253 248
        }
254 249

  
255 250
    }
b/trunk/Pithos.Core/Agents/NetworkAgent.cs
40 40
 */
41 41
#endregion
42 42

  
43
//TODO: Now there is a UUID tag. This can be used for renames/moves
44

  
45

  
46 43
using System;
47
using System.Collections.Concurrent;
48 44
using System.Collections.Generic;
49 45
using System.ComponentModel.Composition;
50 46
using System.Diagnostics;
51 47
using System.Diagnostics.Contracts;
52 48
using System.IO;
53
using System.Linq;
54 49
using System.Net;
55 50
using System.Threading;
56 51
using System.Threading.Tasks;
57
using System.Threading.Tasks.Dataflow;
58 52
using Castle.ActiveRecord;
59 53
using Pithos.Interfaces;
60 54
using Pithos.Network;
......
62 56

  
63 57
namespace Pithos.Core.Agents
64 58
{
65
    //TODO: Ensure all network operations use exact casing. Pithos is case sensitive
66 59
    [Export]
67 60
    public class NetworkAgent
68 61
    {
......
78 71

  
79 72
        private static readonly ILog Log = LogManager.GetLogger("NetworkAgent");
80 73

  
81
        private readonly ConcurrentBag<AccountInfo> _accounts = new ConcurrentBag<AccountInfo>();
82

  
83 74
        [System.ComponentModel.Composition.Import]
84 75
        public IPithosSettings Settings { get; set; }
85 76

  
......
144 135
                    {
145 136
                        //Clear the status of already deleted files to avoid reprocessing
146 137
                        if (action.LocalFile != null)
147
                            this.StatusKeeper.ClearFileStatus(action.LocalFile.FullName);
138
                            StatusKeeper.ClearFileStatus(action.LocalFile.FullName);
148 139
                    }
149 140
                    else
150 141
                    {
......
676 667
                    var accountName = parts[1];
677 668
                    var oldName = accountInfo.UserName;
678 669
                    var absoluteUri = accountInfo.StorageUri.AbsoluteUri;
679
                    var nameIndex = absoluteUri.IndexOf(oldName);
670
                    var nameIndex = absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
680 671
                    var root = absoluteUri.Substring(0, nameIndex);
681 672

  
682 673
                    accountInfo = new AccountInfo
......
852 843
        }
853 844

  
854 845

  
855
        public void AddAccount(AccountInfo accountInfo)
856
        {            
857
            if (!_accounts.Contains(accountInfo))
858
                _accounts.Add(accountInfo);
859
        }
860 846
    }
861 847

  
862 848
   
b/trunk/Pithos.Core/Agents/PollAgent.cs
47 47
using System.IO;
48 48
using System.Threading;
49 49
using System.Threading.Tasks;
50
using System.Threading.Tasks.Dataflow;
51 50
using Castle.ActiveRecord;
52 51
using Pithos.Interfaces;
53 52
using Pithos.Network;
......
58 57
    using System;
59 58
    using System.Collections.Generic;
60 59
    using System.Linq;
61
    using System.Text;
62 60

  
63 61
    /// <summary>
64 62
    /// PollAgent periodically polls the server to detect object changes. The agent retrieves a listing of all
......
87 85
        //The Sync Event signals a manual synchronisation
88 86
        private readonly AsyncManualResetEvent _syncEvent = new AsyncManualResetEvent();
89 87

  
90
        private ConcurrentDictionary<string, DateTime> _lastSeen = new ConcurrentDictionary<string, DateTime>();
91
        private readonly ConcurrentBag<AccountInfo> _accounts = new ConcurrentBag<AccountInfo>();
88
        private readonly ConcurrentDictionary<string, DateTime> _lastSeen = new ConcurrentDictionary<string, DateTime>();
89
        private readonly ConcurrentDictionary<string, AccountInfo> _accounts = new ConcurrentDictionary<string,AccountInfo>();
92 90

  
93 91

  
94 92
        /// <summary>
......
111 109
            UpdateStatus(PithosStatus.Syncing);
112 110
            StatusNotification.Notify(new PollNotification());
113 111

  
114
            using (log4net.ThreadContext.Stacks["Retrieve Remote"].Push("All accounts"))
112
            using (ThreadContext.Stacks["Retrieve Remote"].Push("All accounts"))
115 113
            {
116 114
                //If this poll fails, we will retry with the same since value
117 115
                var nextSince = since;
......
121 119
                    //This is done to ensure there are no discrepancies due to clock differences
122 120
                    var current = DateTime.Now.AddSeconds(-1);
123 121

  
124
                    var tasks = from accountInfo in _accounts
122
                    var tasks = from accountInfo in _accounts.Values
125 123
                                select ProcessAccountFiles(accountInfo, since);
126 124

  
127 125
                    await TaskEx.WhenAll(tasks.ToList());
......
195 193
                var client = new CloudFilesClient(accountInfo);
196 194

  
197 195
                //We don't need to check the trash container
198
                var containers = client.ListContainers(accountInfo.UserName).Where(c=>c.Name!="trash");
196
                var containers = client.ListContainers(accountInfo.UserName)
197
                    .Where(c=>c.Name!="trash")
198
                    .ToList();
199 199

  
200 200

  
201 201
                CreateContainerFolders(accountInfo, containers);
......
206 206
                    await NetworkAgent.GetDeleteAwaiter();
207 207
                    //Get the poll time now. We may miss some deletions but it's better to keep a file that was deleted
208 208
                    //than delete a file that was created while we were executing the poll                    
209
                    var pollTime = DateTime.Now;
210 209

  
211 210
                    //Get the list of server objects changed since the last check
212 211
                    //The name of the container is passed as state in order to create a dictionary of tasks in a subsequent step
213 212
                    var listObjects = (from container in containers
214 213
                                       select Task<IList<ObjectInfo>>.Factory.StartNew(_ =>
215 214
                                             client.ListObjects(accountInfo.UserName, container.Name, since), container.Name)).ToList();
216
                    //BUG: Can't detect difference between no changes or no objects
217
                    //ListObjects returns nothing if there are no changes since the last check time (since value)                    
218
                    //TODO: Must detect the difference between no server objects and no change
219

  
220
                    //NOTE: One option is to "mark" all result lists with their container name, or 
221
                    //rather the url of the container
222
                    //Another option 
223 215

  
224 216
                    var listShared = Task<IList<ObjectInfo>>.Factory.StartNew(_ => 
225 217
                        client.ListSharedObjects(since), "shared");
......
263 255

  
264 256
                        var differencer = _differencer.PostSnapshot(accountInfo, cleanRemotes);
265 257

  
266
                        ProcessDeletedFiles(accountInfo, differencer.Deleted.FilterDirectlyBelow(SelectiveUris), pollTime);
258
                        ProcessDeletedFiles(accountInfo, differencer.Deleted.FilterDirectlyBelow(SelectiveUris));
267 259

  
268 260
                        // @@@ NEED To add previous state here as well, To compare with previous hash
269 261

  
......
301 293
            }
302 294
        }
303 295

  
304
        AccountsDifferencer _differencer = new AccountsDifferencer();
296
        readonly AccountsDifferencer _differencer = new AccountsDifferencer();
305 297
        private List<Uri> _selectiveUris=new List<Uri>();
306 298

  
307 299
        /// <summary>
......
309 301
        /// </summary>
310 302
        /// <param name="accountInfo"></param>
311 303
        /// <param name="cloudFiles"></param>
312
        /// <param name="pollTime"></param>
313
        private void ProcessDeletedFiles(AccountInfo accountInfo, IEnumerable<ObjectInfo> cloudFiles, DateTime pollTime)
304
        private void ProcessDeletedFiles(AccountInfo accountInfo, IEnumerable<ObjectInfo> cloudFiles)
314 305
        {
315 306
            if (accountInfo == null)
316 307
                throw new ArgumentNullException("accountInfo");
......
504 495
            }
505 496
        }
506 497

  
507
        private void ProcessTrashedFiles(AccountInfo accountInfo, IEnumerable<ObjectInfo> trashObjects)
508
        {
509
            var fileAgent = FileAgent.GetFileAgent(accountInfo);
510
            foreach (var trashObject in trashObjects)
511
            {
512
                var barePath = trashObject.RelativeUrlToFilePath(accountInfo.UserName);
513
                //HACK: Assume only the "pithos" container is used. Must find out what happens when
514
                //deleting a file from a different container
515
                var relativePath = Path.Combine("pithos", barePath);
516
                fileAgent.Delete(relativePath);
517
            }
518
        }
519

  
520 498
        /// <summary>
521 499
        /// Notify the UI to update the visual status
522 500
        /// </summary>
......
561 539

  
562 540
        public void AddAccount(AccountInfo accountInfo)
563 541
        {
564
            if (!_accounts.Contains(accountInfo))
565
                _accounts.Add(accountInfo);
542
            //Avoid adding a duplicate accountInfo
543
            _accounts.TryAdd(accountInfo.UserName, accountInfo);
544
        }
545

  
546
        public void RemoveAccount(AccountInfo accountInfo)
547
        {
548
            AccountInfo account;
549
            _accounts.TryRemove(accountInfo.UserName,out account);
566 550
        }
567 551
    }
568 552
}
b/trunk/Pithos.Core/PithosMonitor.cs
361 361
        private void StartNetworkAgent()
362 362
        {
363 363

  
364
            NetworkAgent.AddAccount(_accountInfo);
365

  
366 364
            NetworkAgent.StatusNotification = StatusNotification;
367 365
                        
368 366
            NetworkAgent.Start();

Also available in: Unified diff