root / trunk / Pithos.Core / Agents / PollAgent.cs @ db8a9589
History | View | Annotate | Download (25.3 kB)
1 | aa7ac00e | Panagiotis Kanavos | #region |
---|---|---|---|
2 | aa7ac00e | Panagiotis Kanavos | /* ----------------------------------------------------------------------- |
3 | aa7ac00e | Panagiotis Kanavos | * <copyright file="PollAgent.cs" company="GRNet"> |
4 | aa7ac00e | Panagiotis Kanavos | * |
5 | aa7ac00e | Panagiotis Kanavos | * Copyright 2011-2012 GRNET S.A. All rights reserved. |
6 | aa7ac00e | Panagiotis Kanavos | * |
7 | aa7ac00e | Panagiotis Kanavos | * Redistribution and use in source and binary forms, with or |
8 | aa7ac00e | Panagiotis Kanavos | * without modification, are permitted provided that the following |
9 | aa7ac00e | Panagiotis Kanavos | * conditions are met: |
10 | aa7ac00e | Panagiotis Kanavos | * |
11 | aa7ac00e | Panagiotis Kanavos | * 1. Redistributions of source code must retain the above |
12 | aa7ac00e | Panagiotis Kanavos | * copyright notice, this list of conditions and the following |
13 | aa7ac00e | Panagiotis Kanavos | * disclaimer. |
14 | aa7ac00e | Panagiotis Kanavos | * |
15 | aa7ac00e | Panagiotis Kanavos | * 2. Redistributions in binary form must reproduce the above |
16 | aa7ac00e | Panagiotis Kanavos | * copyright notice, this list of conditions and the following |
17 | aa7ac00e | Panagiotis Kanavos | * disclaimer in the documentation and/or other materials |
18 | aa7ac00e | Panagiotis Kanavos | * provided with the distribution. |
19 | aa7ac00e | Panagiotis Kanavos | * |
20 | aa7ac00e | Panagiotis Kanavos | * |
21 | aa7ac00e | Panagiotis Kanavos | * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
22 | aa7ac00e | Panagiotis Kanavos | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
23 | aa7ac00e | Panagiotis Kanavos | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
24 | aa7ac00e | Panagiotis Kanavos | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
25 | aa7ac00e | Panagiotis Kanavos | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
26 | aa7ac00e | Panagiotis Kanavos | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
27 | aa7ac00e | Panagiotis Kanavos | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
28 | aa7ac00e | Panagiotis Kanavos | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
29 | aa7ac00e | Panagiotis Kanavos | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
30 | aa7ac00e | Panagiotis Kanavos | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
31 | aa7ac00e | Panagiotis Kanavos | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
32 | aa7ac00e | Panagiotis Kanavos | * POSSIBILITY OF SUCH DAMAGE. |
33 | aa7ac00e | Panagiotis Kanavos | * |
34 | aa7ac00e | Panagiotis Kanavos | * The views and conclusions contained in the software and |
35 | aa7ac00e | Panagiotis Kanavos | * documentation are those of the authors and should not be |
36 | aa7ac00e | Panagiotis Kanavos | * interpreted as representing official policies, either expressed |
37 | aa7ac00e | Panagiotis Kanavos | * or implied, of GRNET S.A. |
38 | aa7ac00e | Panagiotis Kanavos | * </copyright> |
39 | aa7ac00e | Panagiotis Kanavos | * ----------------------------------------------------------------------- |
40 | aa7ac00e | Panagiotis Kanavos | */ |
41 | aa7ac00e | Panagiotis Kanavos | #endregion |
42 | aa7ac00e | Panagiotis Kanavos | |
43 | aa7ac00e | Panagiotis Kanavos | using System.Collections.Concurrent; |
44 | aa7ac00e | Panagiotis Kanavos | using System.ComponentModel.Composition; |
45 | aa7ac00e | Panagiotis Kanavos | using System.Diagnostics; |
46 | aa7ac00e | Panagiotis Kanavos | using System.Diagnostics.Contracts; |
47 | aa7ac00e | Panagiotis Kanavos | using System.IO; |
48 | db8a9589 | Panagiotis Kanavos | using System.Reflection; |
49 | aa7ac00e | Panagiotis Kanavos | using System.Threading; |
50 | aa7ac00e | Panagiotis Kanavos | using System.Threading.Tasks; |
51 | aa7ac00e | Panagiotis Kanavos | using Castle.ActiveRecord; |
52 | aa7ac00e | Panagiotis Kanavos | using Pithos.Interfaces; |
53 | aa7ac00e | Panagiotis Kanavos | using Pithos.Network; |
54 | aa7ac00e | Panagiotis Kanavos | using log4net; |
55 | aa7ac00e | Panagiotis Kanavos | |
56 | aa7ac00e | Panagiotis Kanavos | namespace Pithos.Core.Agents |
57 | aa7ac00e | Panagiotis Kanavos | { |
58 | aa7ac00e | Panagiotis Kanavos | using System; |
59 | aa7ac00e | Panagiotis Kanavos | using System.Collections.Generic; |
60 | aa7ac00e | Panagiotis Kanavos | using System.Linq; |
61 | aa7ac00e | Panagiotis Kanavos | |
62 | aa7ac00e | Panagiotis Kanavos | /// <summary> |
63 | aa7ac00e | Panagiotis Kanavos | /// PollAgent periodically polls the server to detect object changes. The agent retrieves a listing of all |
64 | aa7ac00e | Panagiotis Kanavos | /// objects and compares it with a previously cached version to detect differences. |
65 | aa7ac00e | Panagiotis Kanavos | /// New files are downloaded, missing files are deleted from the local file system and common files are compared |
66 | aa7ac00e | Panagiotis Kanavos | /// to determine the appropriate action |
67 | aa7ac00e | Panagiotis Kanavos | /// </summary> |
68 | aa7ac00e | Panagiotis Kanavos | [Export] |
69 | aa7ac00e | Panagiotis Kanavos | public class PollAgent |
70 | aa7ac00e | Panagiotis Kanavos | { |
71 | db8a9589 | Panagiotis Kanavos | private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
72 | aa7ac00e | Panagiotis Kanavos | |
73 | aa7ac00e | Panagiotis Kanavos | [System.ComponentModel.Composition.Import] |
74 | aa7ac00e | Panagiotis Kanavos | public IStatusKeeper StatusKeeper { get; set; } |
75 | aa7ac00e | Panagiotis Kanavos | |
76 | aa7ac00e | Panagiotis Kanavos | [System.ComponentModel.Composition.Import] |
77 | aa7ac00e | Panagiotis Kanavos | public IPithosSettings Settings { get; set; } |
78 | aa7ac00e | Panagiotis Kanavos | |
79 | aa7ac00e | Panagiotis Kanavos | [System.ComponentModel.Composition.Import] |
80 | aa7ac00e | Panagiotis Kanavos | public NetworkAgent NetworkAgent { get; set; } |
81 | aa7ac00e | Panagiotis Kanavos | |
82 | aa7ac00e | Panagiotis Kanavos | public IStatusNotification StatusNotification { get; set; } |
83 | aa7ac00e | Panagiotis Kanavos | |
84 | aa7ac00e | Panagiotis Kanavos | private bool _firstPoll = true; |
85 | aa7ac00e | Panagiotis Kanavos | |
86 | aa7ac00e | Panagiotis Kanavos | //The Sync Event signals a manual synchronisation |
87 | aa7ac00e | Panagiotis Kanavos | private readonly AsyncManualResetEvent _syncEvent = new AsyncManualResetEvent(); |
88 | aa7ac00e | Panagiotis Kanavos | |
89 | ec1a1baf | Panagiotis Kanavos | private readonly ConcurrentDictionary<string, DateTime> _lastSeen = new ConcurrentDictionary<string, DateTime>(); |
90 | ec1a1baf | Panagiotis Kanavos | private readonly ConcurrentDictionary<string, AccountInfo> _accounts = new ConcurrentDictionary<string,AccountInfo>(); |
91 | aa7ac00e | Panagiotis Kanavos | |
92 | aa7ac00e | Panagiotis Kanavos | |
93 | aa7ac00e | Panagiotis Kanavos | /// <summary> |
94 | aa7ac00e | Panagiotis Kanavos | /// Start a manual synchronization |
95 | aa7ac00e | Panagiotis Kanavos | /// </summary> |
96 | aa7ac00e | Panagiotis Kanavos | public void SynchNow() |
97 | aa7ac00e | Panagiotis Kanavos | { |
98 | aa7ac00e | Panagiotis Kanavos | _syncEvent.Set(); |
99 | aa7ac00e | Panagiotis Kanavos | } |
100 | aa7ac00e | Panagiotis Kanavos | |
101 | fec5da06 | Panagiotis Kanavos | /// <summary> |
102 | fec5da06 | Panagiotis Kanavos | /// Remote files are polled periodically. Any changes are processed |
103 | fec5da06 | Panagiotis Kanavos | /// </summary> |
104 | fec5da06 | Panagiotis Kanavos | /// <param name="since"></param> |
105 | fec5da06 | Panagiotis Kanavos | /// <returns></returns> |
106 | aa7ac00e | Panagiotis Kanavos | public async Task PollRemoteFiles(DateTime? since = null) |
107 | aa7ac00e | Panagiotis Kanavos | { |
108 | aa7ac00e | Panagiotis Kanavos | Debug.Assert(Thread.CurrentThread.IsBackground, "Polling Ended up in the main thread!"); |
109 | aa7ac00e | Panagiotis Kanavos | |
110 | aa7ac00e | Panagiotis Kanavos | UpdateStatus(PithosStatus.Syncing); |
111 | aa7ac00e | Panagiotis Kanavos | StatusNotification.Notify(new PollNotification()); |
112 | aa7ac00e | Panagiotis Kanavos | |
113 | ec1a1baf | Panagiotis Kanavos | using (ThreadContext.Stacks["Retrieve Remote"].Push("All accounts")) |
114 | aa7ac00e | Panagiotis Kanavos | { |
115 | aa7ac00e | Panagiotis Kanavos | //If this poll fails, we will retry with the same since value |
116 | aa7ac00e | Panagiotis Kanavos | var nextSince = since; |
117 | aa7ac00e | Panagiotis Kanavos | try |
118 | aa7ac00e | Panagiotis Kanavos | { |
119 | aa7ac00e | Panagiotis Kanavos | //Next time we will check for all changes since the current check minus 1 second |
120 | aa7ac00e | Panagiotis Kanavos | //This is done to ensure there are no discrepancies due to clock differences |
121 | aa7ac00e | Panagiotis Kanavos | var current = DateTime.Now.AddSeconds(-1); |
122 | aa7ac00e | Panagiotis Kanavos | |
123 | ec1a1baf | Panagiotis Kanavos | var tasks = from accountInfo in _accounts.Values |
124 | aa7ac00e | Panagiotis Kanavos | select ProcessAccountFiles(accountInfo, since); |
125 | aa7ac00e | Panagiotis Kanavos | |
126 | aa7ac00e | Panagiotis Kanavos | await TaskEx.WhenAll(tasks.ToList()); |
127 | aa7ac00e | Panagiotis Kanavos | |
128 | aa7ac00e | Panagiotis Kanavos | _firstPoll = false; |
129 | aa7ac00e | Panagiotis Kanavos | //Reschedule the poll with the current timestamp as a "since" value |
130 | aa7ac00e | Panagiotis Kanavos | nextSince = current; |
131 | aa7ac00e | Panagiotis Kanavos | } |
132 | aa7ac00e | Panagiotis Kanavos | catch (Exception ex) |
133 | aa7ac00e | Panagiotis Kanavos | { |
134 | aa7ac00e | Panagiotis Kanavos | Log.ErrorFormat("Error while processing accounts\r\n{0}", ex); |
135 | aa7ac00e | Panagiotis Kanavos | //In case of failure retry with the same "since" value |
136 | aa7ac00e | Panagiotis Kanavos | } |
137 | aa7ac00e | Panagiotis Kanavos | |
138 | aa7ac00e | Panagiotis Kanavos | UpdateStatus(PithosStatus.InSynch); |
139 | fec5da06 | Panagiotis Kanavos | //The multiple try blocks are required because we can't have an await call |
140 | fec5da06 | Panagiotis Kanavos | //inside a finally block |
141 | fec5da06 | Panagiotis Kanavos | //TODO: Find a more elegant solution for reschedulling in the event of an exception |
142 | fec5da06 | Panagiotis Kanavos | try |
143 | fec5da06 | Panagiotis Kanavos | { |
144 | fec5da06 | Panagiotis Kanavos | //Wait for the polling interval to pass or the Sync event to be signalled |
145 | fec5da06 | Panagiotis Kanavos | nextSince = await WaitForScheduledOrManualPoll(nextSince); |
146 | fec5da06 | Panagiotis Kanavos | } |
147 | fec5da06 | Panagiotis Kanavos | finally |
148 | fec5da06 | Panagiotis Kanavos | { |
149 | fec5da06 | Panagiotis Kanavos | //Ensure polling is scheduled even in case of error |
150 | fec5da06 | Panagiotis Kanavos | TaskEx.Run(() => PollRemoteFiles(nextSince)); |
151 | fec5da06 | Panagiotis Kanavos | } |
152 | aa7ac00e | Panagiotis Kanavos | } |
153 | aa7ac00e | Panagiotis Kanavos | } |
154 | aa7ac00e | Panagiotis Kanavos | |
155 | aa7ac00e | Panagiotis Kanavos | /// <summary> |
156 | aa7ac00e | Panagiotis Kanavos | /// Wait for the polling period to expire or a manual sync request |
157 | aa7ac00e | Panagiotis Kanavos | /// </summary> |
158 | aa7ac00e | Panagiotis Kanavos | /// <param name="since"></param> |
159 | aa7ac00e | Panagiotis Kanavos | /// <returns></returns> |
160 | aa7ac00e | Panagiotis Kanavos | private async Task<DateTime?> WaitForScheduledOrManualPoll(DateTime? since) |
161 | aa7ac00e | Panagiotis Kanavos | { |
162 | aa7ac00e | Panagiotis Kanavos | var sync = _syncEvent.WaitAsync(); |
163 | aa7ac00e | Panagiotis Kanavos | var wait = TaskEx.Delay(TimeSpan.FromSeconds(Settings.PollingInterval), NetworkAgent.CancellationToken); |
164 | aa7ac00e | Panagiotis Kanavos | var signaledTask = await TaskEx.WhenAny(sync, wait); |
165 | aa7ac00e | Panagiotis Kanavos | |
166 | aa7ac00e | Panagiotis Kanavos | //Wait for network processing to finish before polling |
167 | 38ac43a6 | Panagiotis Kanavos | var pauseTask=NetworkAgent.ProceedEvent.WaitAsync(); |
168 | aa7ac00e | Panagiotis Kanavos | await TaskEx.WhenAll(signaledTask, pauseTask); |
169 | aa7ac00e | Panagiotis Kanavos | |
170 | aa7ac00e | Panagiotis Kanavos | //If polling is signalled by SynchNow, ignore the since tag |
171 | aa7ac00e | Panagiotis Kanavos | if (sync.IsCompleted) |
172 | aa7ac00e | Panagiotis Kanavos | { |
173 | aa7ac00e | Panagiotis Kanavos | //TODO: Must convert to AutoReset |
174 | aa7ac00e | Panagiotis Kanavos | _syncEvent.Reset(); |
175 | aa7ac00e | Panagiotis Kanavos | return null; |
176 | aa7ac00e | Panagiotis Kanavos | } |
177 | aa7ac00e | Panagiotis Kanavos | return since; |
178 | aa7ac00e | Panagiotis Kanavos | } |
179 | aa7ac00e | Panagiotis Kanavos | |
180 | aa7ac00e | Panagiotis Kanavos | public async Task ProcessAccountFiles(AccountInfo accountInfo, DateTime? since = null) |
181 | aa7ac00e | Panagiotis Kanavos | { |
182 | aa7ac00e | Panagiotis Kanavos | if (accountInfo == null) |
183 | aa7ac00e | Panagiotis Kanavos | throw new ArgumentNullException("accountInfo"); |
184 | aa7ac00e | Panagiotis Kanavos | if (String.IsNullOrWhiteSpace(accountInfo.AccountPath)) |
185 | aa7ac00e | Panagiotis Kanavos | throw new ArgumentException("The AccountInfo.AccountPath is empty", "accountInfo"); |
186 | aa7ac00e | Panagiotis Kanavos | Contract.EndContractBlock(); |
187 | aa7ac00e | Panagiotis Kanavos | |
188 | aa7ac00e | Panagiotis Kanavos | |
189 | aa7ac00e | Panagiotis Kanavos | using (log4net.ThreadContext.Stacks["Retrieve Remote"].Push(accountInfo.UserName)) |
190 | aa7ac00e | Panagiotis Kanavos | { |
191 | aa7ac00e | Panagiotis Kanavos | await NetworkAgent.GetDeleteAwaiter(); |
192 | aa7ac00e | Panagiotis Kanavos | |
193 | aa7ac00e | Panagiotis Kanavos | Log.Info("Scheduled"); |
194 | aa7ac00e | Panagiotis Kanavos | var client = new CloudFilesClient(accountInfo); |
195 | aa7ac00e | Panagiotis Kanavos | |
196 | 99e6329f | Panagiotis Kanavos | //We don't need to check the trash container |
197 | ec1a1baf | Panagiotis Kanavos | var containers = client.ListContainers(accountInfo.UserName) |
198 | ec1a1baf | Panagiotis Kanavos | .Where(c=>c.Name!="trash") |
199 | ec1a1baf | Panagiotis Kanavos | .ToList(); |
200 | aa7ac00e | Panagiotis Kanavos | |
201 | aa7ac00e | Panagiotis Kanavos | |
202 | aa7ac00e | Panagiotis Kanavos | CreateContainerFolders(accountInfo, containers); |
203 | aa7ac00e | Panagiotis Kanavos | |
204 | aa7ac00e | Panagiotis Kanavos | try |
205 | aa7ac00e | Panagiotis Kanavos | { |
206 | 759bd3c4 | Panagiotis Kanavos | //Wait for any deletions to finish |
207 | aa7ac00e | Panagiotis Kanavos | await NetworkAgent.GetDeleteAwaiter(); |
208 | aa7ac00e | Panagiotis Kanavos | //Get the poll time now. We may miss some deletions but it's better to keep a file that was deleted |
209 | aa7ac00e | Panagiotis Kanavos | //than delete a file that was created while we were executing the poll |
210 | aa7ac00e | Panagiotis Kanavos | |
211 | aa7ac00e | Panagiotis Kanavos | //Get the list of server objects changed since the last check |
212 | aa7ac00e | Panagiotis Kanavos | //The name of the container is passed as state in order to create a dictionary of tasks in a subsequent step |
213 | aa7ac00e | Panagiotis Kanavos | var listObjects = (from container in containers |
214 | aa7ac00e | Panagiotis Kanavos | select Task<IList<ObjectInfo>>.Factory.StartNew(_ => |
215 | aa7ac00e | Panagiotis Kanavos | client.ListObjects(accountInfo.UserName, container.Name, since), container.Name)).ToList(); |
216 | 99e6329f | Panagiotis Kanavos | |
217 | 99e6329f | Panagiotis Kanavos | var listShared = Task<IList<ObjectInfo>>.Factory.StartNew(_ => |
218 | 99e6329f | Panagiotis Kanavos | client.ListSharedObjects(since), "shared"); |
219 | aa7ac00e | Panagiotis Kanavos | listObjects.Add(listShared); |
220 | aa7ac00e | Panagiotis Kanavos | var listTasks = await Task.Factory.WhenAll(listObjects.ToArray()); |
221 | aa7ac00e | Panagiotis Kanavos | |
222 | aa7ac00e | Panagiotis Kanavos | using (log4net.ThreadContext.Stacks["SCHEDULE"].Push("Process Results")) |
223 | aa7ac00e | Panagiotis Kanavos | { |
224 | aa7ac00e | Panagiotis Kanavos | var dict = listTasks.ToDictionary(t => t.AsyncState); |
225 | aa7ac00e | Panagiotis Kanavos | |
226 | aa7ac00e | Panagiotis Kanavos | //Get all non-trash objects. Remember, the container name is stored in AsyncState |
227 | aa7ac00e | Panagiotis Kanavos | var remoteObjects = from objectList in listTasks |
228 | aa7ac00e | Panagiotis Kanavos | where (string)objectList.AsyncState != "trash" |
229 | aa7ac00e | Panagiotis Kanavos | from obj in objectList.Result |
230 | aa7ac00e | Panagiotis Kanavos | select obj; |
231 | aa7ac00e | Panagiotis Kanavos | |
232 | aa7ac00e | Panagiotis Kanavos | var sharedObjects = dict["shared"].Result; |
233 | aa7ac00e | Panagiotis Kanavos | |
234 | aa7ac00e | Panagiotis Kanavos | //DON'T process trashed files |
235 | aa7ac00e | Panagiotis Kanavos | //If some files are deleted and added again to a folder, they will be deleted |
236 | aa7ac00e | Panagiotis Kanavos | //even though they are new. |
237 | aa7ac00e | Panagiotis Kanavos | //We would have to check file dates and hashes to ensure that a trashed file |
238 | aa7ac00e | Panagiotis Kanavos | //can be deleted safely from the local hard drive. |
239 | aa7ac00e | Panagiotis Kanavos | /* |
240 | aa7ac00e | Panagiotis Kanavos | //Items with the same name, hash may be both in the container and the trash |
241 | aa7ac00e | Panagiotis Kanavos | //Don't delete items that exist in the container |
242 | aa7ac00e | Panagiotis Kanavos | var realTrash = from trash in trashObjects |
243 | aa7ac00e | Panagiotis Kanavos | where |
244 | aa7ac00e | Panagiotis Kanavos | !remoteObjects.Any( |
245 | aa7ac00e | Panagiotis Kanavos | info => info.Name == trash.Name && info.Hash == trash.Hash) |
246 | aa7ac00e | Panagiotis Kanavos | select trash; |
247 | aa7ac00e | Panagiotis Kanavos | ProcessTrashedFiles(accountInfo, realTrash); |
248 | aa7ac00e | Panagiotis Kanavos | */ |
249 | aa7ac00e | Panagiotis Kanavos | |
250 | aa7ac00e | Panagiotis Kanavos | var cleanRemotes = (from info in remoteObjects.Union(sharedObjects) |
251 | 92f18b56 | Panagiotis Kanavos | let name = info.Name??"" |
252 | aa7ac00e | Panagiotis Kanavos | where !name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase) && |
253 | aa7ac00e | Panagiotis Kanavos | !name.StartsWith(FolderConstants.CacheFolder + "/", |
254 | aa7ac00e | Panagiotis Kanavos | StringComparison.InvariantCultureIgnoreCase) |
255 | aa7ac00e | Panagiotis Kanavos | select info).ToList(); |
256 | aa7ac00e | Panagiotis Kanavos | |
257 | aa7ac00e | Panagiotis Kanavos | var differencer = _differencer.PostSnapshot(accountInfo, cleanRemotes); |
258 | aa7ac00e | Panagiotis Kanavos | |
259 | ec1a1baf | Panagiotis Kanavos | ProcessDeletedFiles(accountInfo, differencer.Deleted.FilterDirectlyBelow(SelectiveUris)); |
260 | 759bd3c4 | Panagiotis Kanavos | |
261 | 759bd3c4 | Panagiotis Kanavos | // @@@ NEED To add previous state here as well, To compare with previous hash |
262 | 759bd3c4 | Panagiotis Kanavos | |
263 | 759bd3c4 | Panagiotis Kanavos | |
264 | aa7ac00e | Panagiotis Kanavos | |
265 | aa7ac00e | Panagiotis Kanavos | //Create a list of actions from the remote files |
266 | b666b39a | Panagiotis Kanavos | var allActions = MovesToActions(accountInfo,differencer.Moved.FilterDirectlyBelow(SelectiveUris)) |
267 | b666b39a | Panagiotis Kanavos | .Union( |
268 | b666b39a | Panagiotis Kanavos | ChangesToActions(accountInfo, differencer.Changed.FilterDirectlyBelow(SelectiveUris))) |
269 | aa7ac00e | Panagiotis Kanavos | .Union( |
270 | fec5da06 | Panagiotis Kanavos | CreatesToActions(accountInfo, differencer.Created.FilterDirectlyBelow(SelectiveUris))); |
271 | aa7ac00e | Panagiotis Kanavos | |
272 | aa7ac00e | Panagiotis Kanavos | //And remove those that are already being processed by the agent |
273 | aa7ac00e | Panagiotis Kanavos | var distinctActions = allActions |
274 | aa7ac00e | Panagiotis Kanavos | .Except(NetworkAgent.GetEnumerable(), new PithosMonitor.LocalFileComparer()) |
275 | aa7ac00e | Panagiotis Kanavos | .ToList(); |
276 | aa7ac00e | Panagiotis Kanavos | |
277 | aa7ac00e | Panagiotis Kanavos | //Queue all the actions |
278 | aa7ac00e | Panagiotis Kanavos | foreach (var message in distinctActions) |
279 | aa7ac00e | Panagiotis Kanavos | { |
280 | aa7ac00e | Panagiotis Kanavos | NetworkAgent.Post(message); |
281 | aa7ac00e | Panagiotis Kanavos | } |
282 | aa7ac00e | Panagiotis Kanavos | |
283 | aa7ac00e | Panagiotis Kanavos | Log.Info("[LISTENER] End Processing"); |
284 | aa7ac00e | Panagiotis Kanavos | } |
285 | aa7ac00e | Panagiotis Kanavos | } |
286 | aa7ac00e | Panagiotis Kanavos | catch (Exception ex) |
287 | aa7ac00e | Panagiotis Kanavos | { |
288 | aa7ac00e | Panagiotis Kanavos | Log.ErrorFormat("[FAIL] ListObjects for{0} in ProcessRemoteFiles with {1}", accountInfo.UserName, ex); |
289 | aa7ac00e | Panagiotis Kanavos | return; |
290 | aa7ac00e | Panagiotis Kanavos | } |
291 | aa7ac00e | Panagiotis Kanavos | |
292 | aa7ac00e | Panagiotis Kanavos | Log.Info("[LISTENER] Finished"); |
293 | aa7ac00e | Panagiotis Kanavos | |
294 | aa7ac00e | Panagiotis Kanavos | } |
295 | aa7ac00e | Panagiotis Kanavos | } |
296 | aa7ac00e | Panagiotis Kanavos | |
297 | ec1a1baf | Panagiotis Kanavos | readonly AccountsDifferencer _differencer = new AccountsDifferencer(); |
298 | 759bd3c4 | Panagiotis Kanavos | private List<Uri> _selectiveUris=new List<Uri>(); |
299 | aa7ac00e | Panagiotis Kanavos | |
300 | aa7ac00e | Panagiotis Kanavos | /// <summary> |
301 | aa7ac00e | Panagiotis Kanavos | /// Deletes local files that are not found in the list of cloud files |
302 | aa7ac00e | Panagiotis Kanavos | /// </summary> |
303 | aa7ac00e | Panagiotis Kanavos | /// <param name="accountInfo"></param> |
304 | aa7ac00e | Panagiotis Kanavos | /// <param name="cloudFiles"></param> |
305 | ec1a1baf | Panagiotis Kanavos | private void ProcessDeletedFiles(AccountInfo accountInfo, IEnumerable<ObjectInfo> cloudFiles) |
306 | aa7ac00e | Panagiotis Kanavos | { |
307 | aa7ac00e | Panagiotis Kanavos | if (accountInfo == null) |
308 | aa7ac00e | Panagiotis Kanavos | throw new ArgumentNullException("accountInfo"); |
309 | aa7ac00e | Panagiotis Kanavos | if (String.IsNullOrWhiteSpace(accountInfo.AccountPath)) |
310 | aa7ac00e | Panagiotis Kanavos | throw new ArgumentException("The AccountInfo.AccountPath is empty", "accountInfo"); |
311 | aa7ac00e | Panagiotis Kanavos | if (cloudFiles == null) |
312 | aa7ac00e | Panagiotis Kanavos | throw new ArgumentNullException("cloudFiles"); |
313 | aa7ac00e | Panagiotis Kanavos | Contract.EndContractBlock(); |
314 | aa7ac00e | Panagiotis Kanavos | |
315 | aa7ac00e | Panagiotis Kanavos | //On the first run |
316 | aa7ac00e | Panagiotis Kanavos | if (_firstPoll) |
317 | aa7ac00e | Panagiotis Kanavos | { |
318 | aa7ac00e | Panagiotis Kanavos | //Only consider files that are not being modified, ie they are in the Unchanged state |
319 | aa7ac00e | Panagiotis Kanavos | var deleteCandidates = FileState.Queryable.Where(state => |
320 | aa7ac00e | Panagiotis Kanavos | state.FilePath.StartsWith(accountInfo.AccountPath) |
321 | aa7ac00e | Panagiotis Kanavos | && state.FileStatus == FileStatus.Unchanged).ToList(); |
322 | aa7ac00e | Panagiotis Kanavos | |
323 | aa7ac00e | Panagiotis Kanavos | |
324 | aa7ac00e | Panagiotis Kanavos | //TODO: filesToDelete must take into account the Others container |
325 | aa7ac00e | Panagiotis Kanavos | var filesToDelete = (from deleteCandidate in deleteCandidates |
326 | aa7ac00e | Panagiotis Kanavos | let localFile = FileInfoExtensions.FromPath(deleteCandidate.FilePath) |
327 | aa7ac00e | Panagiotis Kanavos | let relativeFilePath = localFile.AsRelativeTo(accountInfo.AccountPath) |
328 | aa7ac00e | Panagiotis Kanavos | where |
329 | aa7ac00e | Panagiotis Kanavos | !cloudFiles.Any(r => r.RelativeUrlToFilePath(accountInfo.UserName) == relativeFilePath) |
330 | aa7ac00e | Panagiotis Kanavos | select localFile).ToList(); |
331 | aa7ac00e | Panagiotis Kanavos | |
332 | aa7ac00e | Panagiotis Kanavos | |
333 | aa7ac00e | Panagiotis Kanavos | |
334 | aa7ac00e | Panagiotis Kanavos | //Set the status of missing files to Conflict |
335 | aa7ac00e | Panagiotis Kanavos | foreach (var item in filesToDelete) |
336 | aa7ac00e | Panagiotis Kanavos | { |
337 | aa7ac00e | Panagiotis Kanavos | //Try to acquire a gate on the file, to take into account files that have been dequeued |
338 | aa7ac00e | Panagiotis Kanavos | //and are being processed |
339 | aa7ac00e | Panagiotis Kanavos | using (var gate = NetworkGate.Acquire(item.FullName, NetworkOperation.Deleting)) |
340 | aa7ac00e | Panagiotis Kanavos | { |
341 | aa7ac00e | Panagiotis Kanavos | if (gate.Failed) |
342 | aa7ac00e | Panagiotis Kanavos | continue; |
343 | aa7ac00e | Panagiotis Kanavos | StatusKeeper.SetFileState(item.FullName, FileStatus.Conflict, FileOverlayStatus.Deleted); |
344 | aa7ac00e | Panagiotis Kanavos | } |
345 | aa7ac00e | Panagiotis Kanavos | } |
346 | aa7ac00e | Panagiotis Kanavos | UpdateStatus(PithosStatus.HasConflicts); |
347 | aa7ac00e | Panagiotis Kanavos | StatusNotification.NotifyConflicts(filesToDelete, String.Format("{0} local files are missing from Pithos, possibly because they were deleted", filesToDelete.Count)); |
348 | aa7ac00e | Panagiotis Kanavos | StatusNotification.NotifyForFiles(filesToDelete, String.Format("{0} files were deleted", filesToDelete.Count), TraceLevel.Info); |
349 | aa7ac00e | Panagiotis Kanavos | } |
350 | aa7ac00e | Panagiotis Kanavos | else |
351 | aa7ac00e | Panagiotis Kanavos | { |
352 | aa7ac00e | Panagiotis Kanavos | var deletedFiles = new List<FileSystemInfo>(); |
353 | aa7ac00e | Panagiotis Kanavos | foreach (var objectInfo in cloudFiles) |
354 | aa7ac00e | Panagiotis Kanavos | { |
355 | aa7ac00e | Panagiotis Kanavos | var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName); |
356 | fbbbe99b | Panagiotis Kanavos | var item = FileAgent.GetFileAgent(accountInfo).GetFileSystemInfo(relativePath); |
357 | aa7ac00e | Panagiotis Kanavos | if (item.Exists) |
358 | aa7ac00e | Panagiotis Kanavos | { |
359 | aa7ac00e | Panagiotis Kanavos | if ((item.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) |
360 | aa7ac00e | Panagiotis Kanavos | { |
361 | aa7ac00e | Panagiotis Kanavos | item.Attributes = item.Attributes & ~FileAttributes.ReadOnly; |
362 | aa7ac00e | Panagiotis Kanavos | |
363 | aa7ac00e | Panagiotis Kanavos | } |
364 | aa7ac00e | Panagiotis Kanavos | item.Delete(); |
365 | aa7ac00e | Panagiotis Kanavos | DateTime lastDate; |
366 | aa7ac00e | Panagiotis Kanavos | _lastSeen.TryRemove(item.FullName, out lastDate); |
367 | aa7ac00e | Panagiotis Kanavos | deletedFiles.Add(item); |
368 | aa7ac00e | Panagiotis Kanavos | } |
369 | aa7ac00e | Panagiotis Kanavos | StatusKeeper.SetFileState(item.FullName, FileStatus.Deleted, FileOverlayStatus.Deleted); |
370 | aa7ac00e | Panagiotis Kanavos | } |
371 | aa7ac00e | Panagiotis Kanavos | StatusNotification.NotifyForFiles(deletedFiles, String.Format("{0} files were deleted", deletedFiles.Count), TraceLevel.Info); |
372 | aa7ac00e | Panagiotis Kanavos | } |
373 | aa7ac00e | Panagiotis Kanavos | |
374 | aa7ac00e | Panagiotis Kanavos | } |
375 | aa7ac00e | Panagiotis Kanavos | |
376 | b666b39a | Panagiotis Kanavos | /// <summary> |
377 | b666b39a | Panagiotis Kanavos | /// Creates a Sync action for each changed server file |
378 | b666b39a | Panagiotis Kanavos | /// </summary> |
379 | b666b39a | Panagiotis Kanavos | /// <param name="accountInfo"></param> |
380 | b666b39a | Panagiotis Kanavos | /// <param name="changes"></param> |
381 | b666b39a | Panagiotis Kanavos | /// <returns></returns> |
382 | aa7ac00e | Panagiotis Kanavos | private IEnumerable<CloudAction> ChangesToActions(AccountInfo accountInfo, IEnumerable<ObjectInfo> changes) |
383 | aa7ac00e | Panagiotis Kanavos | { |
384 | aa7ac00e | Panagiotis Kanavos | if (changes == null) |
385 | aa7ac00e | Panagiotis Kanavos | throw new ArgumentNullException(); |
386 | aa7ac00e | Panagiotis Kanavos | Contract.EndContractBlock(); |
387 | fbbbe99b | Panagiotis Kanavos | var fileAgent = FileAgent.GetFileAgent(accountInfo); |
388 | aa7ac00e | Panagiotis Kanavos | |
389 | aa7ac00e | Panagiotis Kanavos | //In order to avoid multiple iterations over the files, we iterate only once |
390 | aa7ac00e | Panagiotis Kanavos | //over the remote files |
391 | aa7ac00e | Panagiotis Kanavos | foreach (var objectInfo in changes) |
392 | aa7ac00e | Panagiotis Kanavos | { |
393 | aa7ac00e | Panagiotis Kanavos | var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName); |
394 | b666b39a | Panagiotis Kanavos | //If a directory object already exists, we may need to sync it |
395 | aa7ac00e | Panagiotis Kanavos | if (fileAgent.Exists(relativePath)) |
396 | aa7ac00e | Panagiotis Kanavos | { |
397 | aa7ac00e | Panagiotis Kanavos | var localFile = fileAgent.GetFileSystemInfo(relativePath); |
398 | b666b39a | Panagiotis Kanavos | //We don't need to sync directories |
399 | aa7ac00e | Panagiotis Kanavos | if (objectInfo.Content_Type == @"application/directory" && localFile is DirectoryInfo) |
400 | aa7ac00e | Panagiotis Kanavos | continue; |
401 | aa7ac00e | Panagiotis Kanavos | using (new SessionScope(FlushAction.Never)) |
402 | aa7ac00e | Panagiotis Kanavos | { |
403 | aa7ac00e | Panagiotis Kanavos | var state = StatusKeeper.GetStateByFilePath(localFile.FullName); |
404 | aa7ac00e | Panagiotis Kanavos | _lastSeen[localFile.FullName] = DateTime.Now; |
405 | aa7ac00e | Panagiotis Kanavos | //Common files should be checked on a per-case basis to detect differences, which is newer |
406 | aa7ac00e | Panagiotis Kanavos | |
407 | aa7ac00e | Panagiotis Kanavos | yield return new CloudAction(accountInfo, CloudActionType.MustSynch, |
408 | aa7ac00e | Panagiotis Kanavos | localFile, objectInfo, state, accountInfo.BlockSize, |
409 | aa7ac00e | Panagiotis Kanavos | accountInfo.BlockHash); |
410 | aa7ac00e | Panagiotis Kanavos | } |
411 | aa7ac00e | Panagiotis Kanavos | } |
412 | aa7ac00e | Panagiotis Kanavos | else |
413 | aa7ac00e | Panagiotis Kanavos | { |
414 | aa7ac00e | Panagiotis Kanavos | //Remote files should be downloaded |
415 | aa7ac00e | Panagiotis Kanavos | yield return new CloudDownloadAction(accountInfo, objectInfo); |
416 | aa7ac00e | Panagiotis Kanavos | } |
417 | aa7ac00e | Panagiotis Kanavos | } |
418 | aa7ac00e | Panagiotis Kanavos | } |
419 | aa7ac00e | Panagiotis Kanavos | |
420 | b666b39a | Panagiotis Kanavos | /// <summary> |
421 | b666b39a | Panagiotis Kanavos | /// Creates a Local Move action for each moved server file |
422 | b666b39a | Panagiotis Kanavos | /// </summary> |
423 | b666b39a | Panagiotis Kanavos | /// <param name="accountInfo"></param> |
424 | b666b39a | Panagiotis Kanavos | /// <param name="moves"></param> |
425 | b666b39a | Panagiotis Kanavos | /// <returns></returns> |
426 | b666b39a | Panagiotis Kanavos | private IEnumerable<CloudAction> MovesToActions(AccountInfo accountInfo, IEnumerable<ObjectInfo> moves) |
427 | b666b39a | Panagiotis Kanavos | { |
428 | b666b39a | Panagiotis Kanavos | if (moves == null) |
429 | b666b39a | Panagiotis Kanavos | throw new ArgumentNullException(); |
430 | b666b39a | Panagiotis Kanavos | Contract.EndContractBlock(); |
431 | b666b39a | Panagiotis Kanavos | var fileAgent = FileAgent.GetFileAgent(accountInfo); |
432 | b666b39a | Panagiotis Kanavos | |
433 | b666b39a | Panagiotis Kanavos | //In order to avoid multiple iterations over the files, we iterate only once |
434 | b666b39a | Panagiotis Kanavos | //over the remote files |
435 | b666b39a | Panagiotis Kanavos | foreach (var objectInfo in moves) |
436 | b666b39a | Panagiotis Kanavos | { |
437 | b666b39a | Panagiotis Kanavos | var previousRelativepath = objectInfo.Previous.RelativeUrlToFilePath(accountInfo.UserName); |
438 | b666b39a | Panagiotis Kanavos | //If the previous file already exists, we can execute a Move operation |
439 | b666b39a | Panagiotis Kanavos | if (fileAgent.Exists(previousRelativepath)) |
440 | b666b39a | Panagiotis Kanavos | { |
441 | b666b39a | Panagiotis Kanavos | var previousFile = fileAgent.GetFileSystemInfo(previousRelativepath); |
442 | b666b39a | Panagiotis Kanavos | using (new SessionScope(FlushAction.Never)) |
443 | b666b39a | Panagiotis Kanavos | { |
444 | b666b39a | Panagiotis Kanavos | var state = StatusKeeper.GetStateByFilePath(previousFile.FullName); |
445 | b666b39a | Panagiotis Kanavos | _lastSeen[previousFile.FullName] = DateTime.Now; |
446 | b666b39a | Panagiotis Kanavos | |
447 | b666b39a | Panagiotis Kanavos | //For each moved object we need to move both the local file and update |
448 | b666b39a | Panagiotis Kanavos | yield return new CloudAction(accountInfo, CloudActionType.RenameLocal, |
449 | b666b39a | Panagiotis Kanavos | previousFile, objectInfo, state, accountInfo.BlockSize, |
450 | b666b39a | Panagiotis Kanavos | accountInfo.BlockHash); |
451 | b666b39a | Panagiotis Kanavos | //For modified files, we need to download the changes as well |
452 | b666b39a | Panagiotis Kanavos | if (objectInfo.Hash!=objectInfo.PreviousHash) |
453 | b666b39a | Panagiotis Kanavos | yield return new CloudDownloadAction(accountInfo,objectInfo); |
454 | b666b39a | Panagiotis Kanavos | } |
455 | b666b39a | Panagiotis Kanavos | } |
456 | b666b39a | Panagiotis Kanavos | //If the previous file does not exist, we need to download it in the new location |
457 | b666b39a | Panagiotis Kanavos | else |
458 | b666b39a | Panagiotis Kanavos | { |
459 | b666b39a | Panagiotis Kanavos | //Remote files should be downloaded |
460 | b666b39a | Panagiotis Kanavos | yield return new CloudDownloadAction(accountInfo, objectInfo); |
461 | b666b39a | Panagiotis Kanavos | } |
462 | b666b39a | Panagiotis Kanavos | } |
463 | b666b39a | Panagiotis Kanavos | } |
464 | b666b39a | Panagiotis Kanavos | |
465 | b666b39a | Panagiotis Kanavos | |
466 | b666b39a | Panagiotis Kanavos | /// <summary> |
467 | b666b39a | Panagiotis Kanavos | /// Creates a download action for each new server file |
468 | b666b39a | Panagiotis Kanavos | /// </summary> |
469 | b666b39a | Panagiotis Kanavos | /// <param name="accountInfo"></param> |
470 | b666b39a | Panagiotis Kanavos | /// <param name="creates"></param> |
471 | b666b39a | Panagiotis Kanavos | /// <returns></returns> |
472 | aa7ac00e | Panagiotis Kanavos | private IEnumerable<CloudAction> CreatesToActions(AccountInfo accountInfo, IEnumerable<ObjectInfo> creates) |
473 | aa7ac00e | Panagiotis Kanavos | { |
474 | aa7ac00e | Panagiotis Kanavos | if (creates == null) |
475 | aa7ac00e | Panagiotis Kanavos | throw new ArgumentNullException(); |
476 | aa7ac00e | Panagiotis Kanavos | Contract.EndContractBlock(); |
477 | fbbbe99b | Panagiotis Kanavos | var fileAgent = FileAgent.GetFileAgent(accountInfo); |
478 | aa7ac00e | Panagiotis Kanavos | |
479 | aa7ac00e | Panagiotis Kanavos | //In order to avoid multiple iterations over the files, we iterate only once |
480 | aa7ac00e | Panagiotis Kanavos | //over the remote files |
481 | aa7ac00e | Panagiotis Kanavos | foreach (var objectInfo in creates) |
482 | aa7ac00e | Panagiotis Kanavos | { |
483 | aa7ac00e | Panagiotis Kanavos | var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName); |
484 | b666b39a | Panagiotis Kanavos | //If the object already exists, we probably have a conflict |
485 | aa7ac00e | Panagiotis Kanavos | if (fileAgent.Exists(relativePath)) |
486 | aa7ac00e | Panagiotis Kanavos | { |
487 | aa7ac00e | Panagiotis Kanavos | //If a directory object already exists, we don't need to perform any other action |
488 | aa7ac00e | Panagiotis Kanavos | var localFile = fileAgent.GetFileSystemInfo(relativePath); |
489 | aa7ac00e | Panagiotis Kanavos | StatusKeeper.SetFileState(localFile.FullName, FileStatus.Conflict, FileOverlayStatus.Conflict); |
490 | aa7ac00e | Panagiotis Kanavos | } |
491 | aa7ac00e | Panagiotis Kanavos | else |
492 | aa7ac00e | Panagiotis Kanavos | { |
493 | aa7ac00e | Panagiotis Kanavos | //Remote files should be downloaded |
494 | aa7ac00e | Panagiotis Kanavos | yield return new CloudDownloadAction(accountInfo, objectInfo); |
495 | aa7ac00e | Panagiotis Kanavos | } |
496 | aa7ac00e | Panagiotis Kanavos | } |
497 | aa7ac00e | Panagiotis Kanavos | } |
498 | aa7ac00e | Panagiotis Kanavos | |
499 | fec5da06 | Panagiotis Kanavos | /// <summary> |
500 | fec5da06 | Panagiotis Kanavos | /// Notify the UI to update the visual status |
501 | fec5da06 | Panagiotis Kanavos | /// </summary> |
502 | fec5da06 | Panagiotis Kanavos | /// <param name="status"></param> |
503 | aa7ac00e | Panagiotis Kanavos | private void UpdateStatus(PithosStatus status) |
504 | aa7ac00e | Panagiotis Kanavos | { |
505 | fec5da06 | Panagiotis Kanavos | try |
506 | fec5da06 | Panagiotis Kanavos | { |
507 | fec5da06 | Panagiotis Kanavos | StatusKeeper.SetPithosStatus(status); |
508 | fec5da06 | Panagiotis Kanavos | StatusNotification.Notify(new Notification()); |
509 | fec5da06 | Panagiotis Kanavos | } |
510 | fec5da06 | Panagiotis Kanavos | catch (Exception exc) |
511 | fec5da06 | Panagiotis Kanavos | { |
512 | fec5da06 | Panagiotis Kanavos | //Failure is not critical, just log it |
513 | fec5da06 | Panagiotis Kanavos | Log.Warn("Error while updating status", exc); |
514 | fec5da06 | Panagiotis Kanavos | } |
515 | aa7ac00e | Panagiotis Kanavos | } |
516 | aa7ac00e | Panagiotis Kanavos | |
517 | aa7ac00e | Panagiotis Kanavos | private static void CreateContainerFolders(AccountInfo accountInfo, IEnumerable<ContainerInfo> containers) |
518 | aa7ac00e | Panagiotis Kanavos | { |
519 | aa7ac00e | Panagiotis Kanavos | var containerPaths = from container in containers |
520 | aa7ac00e | Panagiotis Kanavos | let containerPath = Path.Combine(accountInfo.AccountPath, container.Name) |
521 | aa7ac00e | Panagiotis Kanavos | where container.Name != FolderConstants.TrashContainer && !Directory.Exists(containerPath) |
522 | aa7ac00e | Panagiotis Kanavos | select containerPath; |
523 | aa7ac00e | Panagiotis Kanavos | |
524 | aa7ac00e | Panagiotis Kanavos | foreach (var path in containerPaths) |
525 | aa7ac00e | Panagiotis Kanavos | { |
526 | aa7ac00e | Panagiotis Kanavos | Directory.CreateDirectory(path); |
527 | aa7ac00e | Panagiotis Kanavos | } |
528 | aa7ac00e | Panagiotis Kanavos | } |
529 | 759bd3c4 | Panagiotis Kanavos | |
530 | fec5da06 | Panagiotis Kanavos | public void SetSyncUris(Uri[] uris) |
531 | fec5da06 | Panagiotis Kanavos | { |
532 | fec5da06 | Panagiotis Kanavos | SelectiveUris=uris.ToList(); |
533 | 759bd3c4 | Panagiotis Kanavos | } |
534 | 759bd3c4 | Panagiotis Kanavos | |
535 | 759bd3c4 | Panagiotis Kanavos | protected List<Uri> SelectiveUris |
536 | 759bd3c4 | Panagiotis Kanavos | { |
537 | 759bd3c4 | Panagiotis Kanavos | get { return _selectiveUris;} |
538 | 759bd3c4 | Panagiotis Kanavos | set { _selectiveUris = value; } |
539 | 759bd3c4 | Panagiotis Kanavos | } |
540 | fec5da06 | Panagiotis Kanavos | |
541 | fec5da06 | Panagiotis Kanavos | public void AddAccount(AccountInfo accountInfo) |
542 | fec5da06 | Panagiotis Kanavos | { |
543 | ec1a1baf | Panagiotis Kanavos | //Avoid adding a duplicate accountInfo |
544 | ec1a1baf | Panagiotis Kanavos | _accounts.TryAdd(accountInfo.UserName, accountInfo); |
545 | ec1a1baf | Panagiotis Kanavos | } |
546 | ec1a1baf | Panagiotis Kanavos | |
547 | ec1a1baf | Panagiotis Kanavos | public void RemoveAccount(AccountInfo accountInfo) |
548 | ec1a1baf | Panagiotis Kanavos | { |
549 | ec1a1baf | Panagiotis Kanavos | AccountInfo account; |
550 | ec1a1baf | Panagiotis Kanavos | _accounts.TryRemove(accountInfo.UserName,out account); |
551 | fec5da06 | Panagiotis Kanavos | } |
552 | aa7ac00e | Panagiotis Kanavos | } |
553 | aa7ac00e | Panagiotis Kanavos | } |