OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "base/rand_util.h" | |
6 #include "chrome/browser/download/download_history.h" | |
7 #include "chrome/browser/history/download_persistent_store_info.h" | |
8 #include "chrome/browser/history/history.h" | |
9 #include "chrome/test/base/in_process_browser_test.h" | |
10 #include "content/public/test/mock_download_item.h" | |
11 #include "content/public/test/mock_download_manager.h" | |
12 #include "content/public/test/test_utils.h" | |
13 | |
14 using content::BrowserThread; | |
15 using testing::DoAll; | |
16 using testing::Invoke; | |
17 using testing::Return; | |
18 using testing::ReturnRef; | |
19 using testing::SetArgPointee; | |
20 using testing::WithArg; | |
21 using testing::_; | |
22 | |
23 namespace { | |
24 | |
25 void CheckInfoEqual(const DownloadPersistentStoreInfo& left, | |
26 const DownloadPersistentStoreInfo& right) { | |
27 CHECK_EQ(left.path.value(), right.path.value()); | |
28 CHECK_EQ(left.url.spec(), right.url.spec()); | |
29 CHECK_EQ(left.referrer_url.spec(), right.referrer_url.spec()); | |
30 CHECK_EQ(left.start_time.ToTimeT(), right.start_time.ToTimeT()); | |
31 CHECK_EQ(left.end_time.ToTimeT(), right.end_time.ToTimeT()); | |
32 CHECK_EQ(left.received_bytes, right.received_bytes); | |
33 CHECK_EQ(left.total_bytes, right.total_bytes); | |
34 CHECK_EQ(left.state, right.state); | |
35 CHECK_EQ(left.db_handle, right.db_handle); | |
36 CHECK_EQ(left.opened, right.opened); | |
37 } | |
38 | |
39 typedef std::set<int64> HandleSet; | |
40 typedef std::vector<DownloadPersistentStoreInfo> InfoVector; | |
41 | |
42 class FakeHistoryService : public HistoryService { | |
43 public: | |
44 FakeHistoryService() | |
45 : slow_create_download_(false), | |
46 handle_counter_(0) { | |
47 } | |
48 | |
49 virtual Handle QueryDownloads( | |
50 CancelableRequestConsumerBase* consumer, | |
51 const HistoryService::DownloadQueryCallback& callback) OVERRIDE { | |
52 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
53 base::Bind(&FakeHistoryService::QueryDownloadsDone, | |
54 base::Unretained(this), callback)); | |
55 // DownloadHistory ignores the handle. | |
56 return -1; | |
57 } | |
58 | |
59 void QueryDownloadsDone( | |
60 const HistoryService::DownloadQueryCallback& callback) { | |
61 CHECK(expect_query_downloads_.get()); | |
62 callback.Run(expect_query_downloads_.get()); | |
63 expect_query_downloads_.reset(); | |
64 } | |
65 | |
66 virtual Handle GetVisibleVisitCountToHost( | |
67 const GURL& referrer_url, | |
68 CancelableRequestConsumerBase* consumer, | |
69 const HistoryService::GetVisibleVisitCountToHostCallback& | |
70 callback) OVERRIDE { | |
71 NOTIMPLEMENTED(); | |
72 return -1; | |
73 } | |
74 | |
75 void set_slow_create_download(bool slow) { slow_create_download_ = slow; } | |
76 | |
77 virtual Handle CreateDownload( | |
78 const DownloadPersistentStoreInfo& info, | |
79 CancelableRequestConsumerBase* consumer, | |
80 const HistoryService::DownloadCreateCallback& callback) OVERRIDE { | |
81 create_download_info_ = info; | |
82 create_download_callback_ = base::Bind(callback, handle_counter_++); | |
83 if (!slow_create_download_) | |
84 FinishCreateDownload(); | |
85 return -1; | |
86 } | |
87 | |
88 void FinishCreateDownload() { | |
89 create_download_callback_.Run(); | |
90 create_download_callback_.Reset(); | |
91 } | |
92 | |
93 virtual void UpdateDownload( | |
94 const DownloadPersistentStoreInfo& info) OVERRIDE { | |
95 update_download_ = info; | |
96 } | |
97 | |
98 virtual void RemoveDownloads(const HandleSet& handles) OVERRIDE { | |
99 for (HandleSet::const_iterator it = handles.begin(); | |
100 it != handles.end(); ++it) { | |
101 remove_downloads_.insert(*it); | |
102 } | |
103 } | |
104 | |
105 void ExpectWillQueryDownloads(scoped_ptr<InfoVector> infos) { | |
106 expect_query_downloads_ = infos.Pass(); | |
107 } | |
108 | |
109 void ExpectQueryDownloadsDone() { | |
110 CHECK(NULL == expect_query_downloads_.get()); | |
111 } | |
112 | |
113 void ExpectDownloadCreated( | |
114 const DownloadPersistentStoreInfo& info) { | |
115 content::RunAllPendingInMessageLoop(content::BrowserThread::UI); | |
116 CheckInfoEqual(info, create_download_info_); | |
117 create_download_info_ = DownloadPersistentStoreInfo(); | |
118 } | |
119 | |
120 void ExpectNoDownloadCreated() { | |
121 content::RunAllPendingInMessageLoop(content::BrowserThread::UI); | |
122 CheckInfoEqual(DownloadPersistentStoreInfo(), create_download_info_); | |
123 } | |
124 | |
125 void ExpectDownloadUpdated(const DownloadPersistentStoreInfo& info) { | |
126 content::RunAllPendingInMessageLoop(content::BrowserThread::UI); | |
127 CheckInfoEqual(update_download_, info); | |
128 update_download_ = DownloadPersistentStoreInfo(); | |
129 } | |
130 | |
131 void ExpectNoDownloadUpdated() { | |
132 content::RunAllPendingInMessageLoop(content::BrowserThread::UI); | |
133 CheckInfoEqual(DownloadPersistentStoreInfo(), update_download_); | |
134 } | |
135 | |
136 void ExpectNoDownloadsRemoved() { | |
137 content::RunAllPendingInMessageLoop(content::BrowserThread::UI); | |
138 CHECK_EQ(0, static_cast<int>(remove_downloads_.size())); | |
139 } | |
140 | |
141 void ExpectDownloadsRemoved(const HandleSet& handles) { | |
142 content::RunAllPendingInMessageLoop(content::BrowserThread::UI); | |
143 HandleSet difference; | |
144 std::insert_iterator<HandleSet> insert_it( | |
145 difference, difference.begin()); | |
146 std::set_difference(handles.begin(), handles.end(), | |
147 remove_downloads_.begin(), | |
148 remove_downloads_.end(), | |
149 insert_it); | |
150 CHECK(difference.empty()); | |
151 remove_downloads_.clear(); | |
152 } | |
153 | |
154 private: | |
155 virtual ~FakeHistoryService() {} | |
156 | |
157 bool slow_create_download_; | |
158 base::Closure create_download_callback_; | |
159 int handle_counter_; | |
160 DownloadPersistentStoreInfo update_download_; | |
161 scoped_ptr<InfoVector> expect_query_downloads_; | |
162 HandleSet remove_downloads_; | |
163 DownloadPersistentStoreInfo create_download_info_; | |
164 | |
165 DISALLOW_COPY_AND_ASSIGN(FakeHistoryService); | |
166 }; | |
167 | |
168 class DownloadHistoryTest : public InProcessBrowserTest { | |
169 public: | |
170 DownloadHistoryTest() | |
171 : manager_(new content::MockDownloadManager()), | |
172 download_history_(NULL), | |
173 manager_observer_(NULL), | |
174 item_observer_(NULL) { | |
175 } | |
176 virtual ~DownloadHistoryTest() {} | |
177 | |
178 protected: | |
179 virtual void CleanUpOnMainThread() OVERRIDE { | |
180 download_history_.reset(); | |
181 } | |
182 | |
183 content::MockDownloadManager& manager() { return *manager_.get(); } | |
184 content::MockDownloadItem& item() { return item_; } | |
185 | |
186 void SetManagerObserver( | |
187 content::DownloadManager::Observer* manager_observer) { | |
188 manager_observer_ = manager_observer; | |
189 } | |
190 content::DownloadManager::Observer* manager_observer() { | |
191 return manager_observer_; | |
192 } | |
193 void SetItemObserver( | |
194 content::DownloadItem::Observer* item_observer) { | |
195 item_observer_ = item_observer; | |
196 } | |
197 content::DownloadItem::Observer* item_observer() { | |
198 return item_observer_; | |
199 } | |
200 | |
201 void ExpectWillQueryDownloads(scoped_ptr<InfoVector> infos) { | |
202 CHECK(infos.get()); | |
203 EXPECT_CALL(manager(), AddObserver(_)).WillOnce(WithArg<0>(Invoke( | |
204 this, &DownloadHistoryTest::SetManagerObserver))); | |
205 EXPECT_CALL(manager(), RemoveObserver(_)); | |
206 if (infos->size() != 0) { | |
207 EXPECT_EQ(1, static_cast<int>(infos->size())); | |
208 EXPECT_CALL(manager(), CreateDownloadItem( | |
209 infos->at(0).path, | |
210 infos->at(0).url, | |
211 infos->at(0).referrer_url, | |
212 infos->at(0).start_time, | |
213 infos->at(0).end_time, | |
214 infos->at(0).received_bytes, | |
215 infos->at(0).total_bytes, | |
216 infos->at(0).state, | |
217 infos->at(0).opened)) | |
218 .WillOnce(DoAll( | |
219 InvokeWithoutArgs( | |
220 this, &DownloadHistoryTest::CallOnDownloadCreated), | |
221 Return(&item()))); | |
222 } | |
223 EXPECT_CALL(manager(), CheckForHistoryFilesRemoval()); | |
224 history_ = new FakeHistoryService(); | |
225 history_->ExpectWillQueryDownloads(infos.Pass()); | |
226 EXPECT_CALL(*manager_.get(), GetAllDownloads(_)).WillRepeatedly(Return()); | |
227 download_history_.reset(new DownloadHistory(&manager(), history_.get())); | |
228 content::RunAllPendingInMessageLoop(content::BrowserThread::UI); | |
229 history_->ExpectQueryDownloadsDone(); | |
230 } | |
231 | |
232 void CallOnDownloadCreated() { | |
233 manager_observer()->OnDownloadCreated(&manager(), &item()); | |
234 } | |
235 | |
236 void set_slow_create_download(bool slow) { | |
237 history_->set_slow_create_download(slow); | |
238 } | |
239 | |
240 void FinishCreateDownload() { | |
241 history_->FinishCreateDownload(); | |
242 } | |
243 | |
244 void ExpectDownloadCreated( | |
245 const DownloadPersistentStoreInfo& info) { | |
246 history_->ExpectDownloadCreated(info); | |
247 } | |
248 | |
249 void ExpectNoDownloadCreated() { | |
250 history_->ExpectNoDownloadCreated(); | |
251 } | |
252 | |
253 void ExpectDownloadUpdated(const DownloadPersistentStoreInfo& info) { | |
254 history_->ExpectDownloadUpdated(info); | |
255 } | |
256 | |
257 void ExpectNoDownloadUpdated() { | |
258 history_->ExpectNoDownloadUpdated(); | |
259 } | |
260 | |
261 void ExpectNoDownloadsRemoved() { | |
262 history_->ExpectNoDownloadsRemoved(); | |
263 } | |
264 | |
265 void ExpectDownloadsRemoved(const HandleSet& handles) { | |
266 history_->ExpectDownloadsRemoved(handles); | |
267 } | |
268 | |
269 void InitItem( | |
270 int32 id, | |
271 const FilePath& path, | |
272 const GURL& url, | |
273 const GURL& referrer, | |
274 const base::Time& start_time, | |
275 const base::Time& end_time, | |
276 int64 received_bytes, | |
277 int64 total_bytes, | |
278 content::DownloadItem::DownloadState state, | |
279 int64 db_handle, | |
280 bool opened, | |
281 DownloadPersistentStoreInfo* info) { | |
282 info->path = path; | |
283 info->url = url; | |
284 info->referrer_url = referrer; | |
285 info->start_time = start_time; | |
286 info->end_time = end_time; | |
287 info->received_bytes = received_bytes; | |
288 info->total_bytes = total_bytes; | |
289 info->state = state; | |
290 info->db_handle = db_handle; | |
291 info->opened = opened; | |
292 EXPECT_CALL(item(), GetId()).WillRepeatedly(Return(id)); | |
293 EXPECT_CALL(item(), GetFullPath()).WillRepeatedly(ReturnRef(path)); | |
294 EXPECT_CALL(item(), GetURL()).WillRepeatedly(ReturnRef(url)); | |
295 EXPECT_CALL(item(), GetReferrerUrl()).WillRepeatedly(ReturnRef(referrer)); | |
296 EXPECT_CALL(item(), GetStartTime()).WillRepeatedly(Return(start_time)); | |
297 EXPECT_CALL(item(), GetEndTime()).WillRepeatedly(Return(end_time)); | |
298 EXPECT_CALL(item(), GetReceivedBytes()) | |
299 .WillRepeatedly(Return(received_bytes)); | |
300 EXPECT_CALL(item(), GetTotalBytes()).WillRepeatedly(Return(total_bytes)); | |
301 EXPECT_CALL(item(), GetState()).WillRepeatedly(Return(state)); | |
302 EXPECT_CALL(item(), GetOpened()).WillRepeatedly(Return(opened)); | |
303 EXPECT_CALL(item(), GetTargetDisposition()).WillRepeatedly(Return( | |
304 content::DownloadItem::TARGET_DISPOSITION_OVERWRITE)); | |
305 EXPECT_CALL(manager(), GetDownload(id)) | |
306 .WillRepeatedly(Return(&item())); | |
307 EXPECT_CALL(item(), AddObserver(_)).WillOnce(WithArg<0>(Invoke( | |
308 this, &DownloadHistoryTest::SetItemObserver))); | |
309 EXPECT_CALL(item(), RemoveObserver(_)); | |
310 std::vector<content::DownloadItem*> items; | |
311 items.push_back(&item()); | |
312 EXPECT_CALL(*manager_.get(), GetAllDownloads(_)) | |
313 .WillRepeatedly(SetArgPointee<0>(items)); | |
314 } | |
315 | |
316 private: | |
317 testing::NiceMock<content::MockDownloadItem> item_; | |
318 scoped_refptr<FakeHistoryService> history_; | |
319 scoped_refptr<content::MockDownloadManager> manager_; | |
320 scoped_ptr<DownloadHistory> download_history_; | |
321 content::DownloadManager::Observer* manager_observer_; | |
322 content::DownloadItem::Observer* item_observer_; | |
323 | |
324 DISALLOW_COPY_AND_ASSIGN(DownloadHistoryTest); | |
325 }; | |
326 | |
327 } // anonymous namespace | |
328 | |
329 // Test loading an item from the database, changing it, saving it back, removing | |
330 // it. | |
331 IN_PROC_BROWSER_TEST_F(DownloadHistoryTest, DownloadHistoryTest_Load) { | |
332 // Load a download from history, create the item, OnDownloadCreated, | |
333 // OnDownloadUpdated, OnDownloadRemoved. | |
334 DownloadPersistentStoreInfo info; | |
335 InitItem(base::RandInt(0, 1 << 20), | |
336 FilePath(FILE_PATH_LITERAL("/foo/bar.pdf")), | |
337 GURL("http://example.com/bar.pdf"), | |
338 GURL("http://example.com/referrer.html"), | |
339 (base::Time::Now() - base::TimeDelta::FromMinutes(10)), | |
340 (base::Time::Now() - base::TimeDelta::FromMinutes(1)), | |
341 100, | |
342 100, | |
343 content::DownloadItem::COMPLETE, | |
344 base::RandInt(0, 1 << 20), | |
345 false, | |
346 &info); | |
347 { | |
348 scoped_ptr<InfoVector> infos(new InfoVector()); | |
349 infos->push_back(info); | |
350 ExpectWillQueryDownloads(infos.Pass()); | |
351 ExpectNoDownloadCreated(); | |
352 } | |
353 | |
354 // Pretend that something changed on the item. | |
355 EXPECT_CALL(item(), GetOpened()).WillRepeatedly(Return(true)); | |
356 item_observer()->OnDownloadUpdated(&item()); | |
357 info.opened = true; | |
358 ExpectDownloadUpdated(info); | |
359 | |
360 // Pretend that the user removed the item. | |
361 HandleSet handles; | |
362 handles.insert(info.db_handle); | |
363 item_observer()->OnDownloadRemoved(&item()); | |
364 ExpectDownloadsRemoved(handles); | |
365 | |
366 // Pretend that the browser is closing. | |
367 manager_observer()->ManagerGoingDown(&manager()); | |
368 item_observer()->OnDownloadDestroyed(&item()); | |
369 } | |
370 | |
371 // Test creating an item, saving it to the database, changing it, saving it | |
372 // back, removing it. | |
373 IN_PROC_BROWSER_TEST_F(DownloadHistoryTest, DownloadHistoryTest_Create) { | |
374 // Create a fresh item not from history, OnDownloadCreated, OnDownloadUpdated, | |
375 // OnDownloadRemoved. | |
376 ExpectWillQueryDownloads(scoped_ptr<InfoVector>(new InfoVector())); | |
377 | |
378 // Note that db_handle must be -1 at first because it isn't in the db yet. | |
379 DownloadPersistentStoreInfo info; | |
380 InitItem(base::RandInt(0, 1 << 20), | |
381 FilePath(FILE_PATH_LITERAL("/foo/bar.pdf")), | |
382 GURL("http://example.com/bar.pdf"), | |
383 GURL("http://example.com/referrer.html"), | |
384 (base::Time::Now() - base::TimeDelta::FromMinutes(10)), | |
385 (base::Time::Now() - base::TimeDelta::FromMinutes(1)), | |
386 100, | |
387 100, | |
388 content::DownloadItem::COMPLETE, | |
389 -1, | |
390 false, | |
391 &info); | |
392 | |
393 // Pretend the manager just created |item|. | |
394 CallOnDownloadCreated(); | |
395 // CreateDownload() always gets db_handle=-1. | |
396 ExpectDownloadCreated(info); | |
397 info.db_handle = 0; | |
398 | |
399 // Pretend that something changed on the item. | |
400 EXPECT_CALL(item(), GetOpened()).WillRepeatedly(Return(true)); | |
401 item_observer()->OnDownloadUpdated(&item()); | |
402 info.opened = true; | |
403 ExpectDownloadUpdated(info); | |
404 | |
405 // Pretend that the user removed the item. | |
406 HandleSet handles; | |
407 handles.insert(info.db_handle); | |
408 item_observer()->OnDownloadRemoved(&item()); | |
409 ExpectDownloadsRemoved(handles); | |
410 | |
411 // Pretend that the browser is closing. | |
412 manager_observer()->ManagerGoingDown(&manager()); | |
413 item_observer()->OnDownloadDestroyed(&item()); | |
414 } | |
415 | |
416 // Test creating a new item, saving it, removing it by setting it Temporary, | |
417 // changing it without saving it back because it's Temporary, clearing | |
418 // IsTemporary, saving it back, changing it, saving it back because it isn't | |
419 // Temporary anymore. | |
420 IN_PROC_BROWSER_TEST_F(DownloadHistoryTest, DownloadHistoryTest_Temporary) { | |
421 // Create a fresh item not from history, OnDownloadCreated, OnDownloadUpdated, | |
422 // OnDownloadRemoved. | |
423 ExpectWillQueryDownloads(scoped_ptr<InfoVector>(new InfoVector())); | |
424 | |
425 // Note that db_handle must be -1 at first because it isn't in the db yet. | |
426 DownloadPersistentStoreInfo info; | |
427 InitItem(base::RandInt(0, 1 << 20), | |
428 FilePath(FILE_PATH_LITERAL("/foo/bar.pdf")), | |
429 GURL("http://example.com/bar.pdf"), | |
430 GURL("http://example.com/referrer.html"), | |
431 (base::Time::Now() - base::TimeDelta::FromMinutes(10)), | |
432 (base::Time::Now() - base::TimeDelta::FromMinutes(1)), | |
433 100, | |
434 100, | |
435 content::DownloadItem::COMPLETE, | |
436 -1, | |
437 false, | |
438 &info); | |
439 | |
440 // Pretend the manager just created |item|. | |
441 CallOnDownloadCreated(); | |
442 // CreateDownload() always gets db_handle=-1. | |
443 ExpectDownloadCreated(info); | |
444 info.db_handle = 0; | |
445 | |
446 // Pretend the item was marked temporary. DownloadHistory should remove it | |
447 // from history and start ignoring it. | |
448 EXPECT_CALL(item(), IsTemporary()).WillRepeatedly(Return(true)); | |
449 item_observer()->OnDownloadUpdated(&item()); | |
450 HandleSet handles; | |
451 handles.insert(info.db_handle); | |
452 ExpectDownloadsRemoved(handles); | |
453 | |
454 // Change something that would make DownloadHistory call UpdateDownload if the | |
455 // item weren't temporary. | |
456 EXPECT_CALL(item(), GetReceivedBytes()).WillRepeatedly(Return(4200)); | |
457 item_observer()->OnDownloadUpdated(&item()); | |
458 ExpectNoDownloadUpdated(); | |
459 | |
460 // Changing a temporary item back to a non-temporary item should make | |
461 // DownloadHistory call CreateDownload. | |
462 EXPECT_CALL(item(), IsTemporary()).WillRepeatedly(Return(false)); | |
463 item_observer()->OnDownloadUpdated(&item()); | |
464 info.received_bytes = 4200; | |
465 info.db_handle = -1; | |
466 // CreateDownload() always gets db_handle=-1. | |
467 ExpectDownloadCreated(info); | |
468 info.db_handle = 1; | |
469 | |
470 EXPECT_CALL(item(), GetReceivedBytes()).WillRepeatedly(Return(100)); | |
471 item_observer()->OnDownloadUpdated(&item()); | |
472 info.received_bytes = 100; | |
473 ExpectDownloadUpdated(info); | |
474 | |
475 // Pretend that the browser is closing. | |
476 manager_observer()->ManagerGoingDown(&manager()); | |
477 item_observer()->OnDownloadDestroyed(&item()); | |
478 } | |
479 | |
480 // Test removing downloads while they're still being added. | |
481 IN_PROC_BROWSER_TEST_F(DownloadHistoryTest, | |
482 DownloadHistoryTest_RemoveWhileAdding) { | |
483 ExpectWillQueryDownloads(scoped_ptr<InfoVector>(new InfoVector())); | |
484 | |
485 // Note that db_handle must be -1 at first because it isn't in the db yet. | |
486 DownloadPersistentStoreInfo info; | |
487 InitItem(base::RandInt(0, 1 << 20), | |
488 FilePath(FILE_PATH_LITERAL("/foo/bar.pdf")), | |
489 GURL("http://example.com/bar.pdf"), | |
490 GURL("http://example.com/referrer.html"), | |
491 (base::Time::Now() - base::TimeDelta::FromMinutes(10)), | |
492 (base::Time::Now() - base::TimeDelta::FromMinutes(1)), | |
493 100, | |
494 100, | |
495 content::DownloadItem::COMPLETE, | |
496 -1, | |
497 false, | |
498 &info); | |
499 | |
500 // Instruct CreateDownload() to not callback to DownloadHistory immediately, | |
501 // but to wait for FinishCreateDownload(). | |
502 set_slow_create_download(true); | |
503 | |
504 // Pretend the manager just created |item|. | |
505 CallOnDownloadCreated(); | |
506 // CreateDownload() always gets db_handle=-1. | |
507 ExpectDownloadCreated(info); | |
508 info.db_handle = 0; | |
509 | |
510 // Call OnDownloadRemoved before calling back to DownloadHistory::ItemAdded(). | |
511 // Instead of calling RemoveDownloads() immediately, it should | |
512 item_observer()->OnDownloadRemoved(&item()); | |
513 EXPECT_CALL(manager(), GetDownload(item().GetId())) | |
514 .WillRepeatedly(Return(static_cast<content::DownloadItem*>(NULL))); | |
515 ExpectNoDownloadsRemoved(); | |
516 | |
517 // Now callback to DownloadHistory::ItemAdded(), and expect a call to | |
518 // RemoveDownloads() for the item that was removed while it was being added. | |
519 FinishCreateDownload(); | |
520 HandleSet handles; | |
521 handles.insert(info.db_handle); | |
522 ExpectDownloadsRemoved(handles); | |
523 | |
524 // Pretend that the browser is closing. | |
525 manager_observer()->ManagerGoingDown(&manager()); | |
526 item_observer()->OnDownloadDestroyed(&item()); | |
527 } | |
Randy Smith (Not in Mondays)
2012/09/24 18:03:25
Any tests to test multiple downloads in flight at
benjhayden
2012/09/24 20:12:11
Made a note to do that before committing.
| |
OLD | NEW |