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