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.
5 #import <Foundation/Foundation.h>
6 #import <ImageCaptureCore/ImageCaptureCore.h>
8 #include "base/files/file.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/mac/cocoa_protocols.h"
12 #include "base/mac/foundation_util.h"
13 #include "base/mac/scoped_nsobject.h"
14 #include "base/mac/sdk_forward_declarations.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/run_loop.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "base/synchronization/waitable_event.h"
19 #include "base/test/sequenced_worker_pool_owner.h"
20 #include "base/threading/sequenced_worker_pool.h"
21 #include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h"
22 #include "components/storage_monitor/image_capture_device_manager.h"
23 #include "components/storage_monitor/test_storage_monitor.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/test/test_browser_thread.h"
26 #include "testing/gtest/include/gtest/gtest.h"
30 const char kDeviceId[] = "id";
31 const char kDevicePath[] = "/ic:id";
32 const char kTestFileContents[] = "test";
36 @interface MockMTPICCameraDevice : ICCameraDevice {
38 base::scoped_nsobject<NSMutableArray> allMediaFiles_;
41 - (void)addMediaFile:(ICCameraFile*)file;
45 @implementation MockMTPICCameraDevice
47 - (NSString*)mountPoint {
55 - (NSString*)UUIDString {
56 return base::SysUTF8ToNSString(kDeviceId);
59 - (ICDeviceType)type {
60 return ICDeviceTypeCamera;
63 - (void)requestOpenSession {
66 - (void)requestCloseSession {
69 - (NSArray*)mediaFiles {
70 return allMediaFiles_;
73 - (void)addMediaFile:(ICCameraFile*)file {
74 if (!allMediaFiles_.get())
75 allMediaFiles_.reset([[NSMutableArray alloc] init]);
76 [allMediaFiles_ addObject:file];
79 - (void)requestDownloadFile:(ICCameraFile*)file
80 options:(NSDictionary*)options
81 downloadDelegate:(id<ICCameraDeviceDownloadDelegate>)downloadDelegate
82 didDownloadSelector:(SEL)selector
83 contextInfo:(void*)contextInfo {
84 base::FilePath saveDir(base::SysNSStringToUTF8(
85 [[options objectForKey:ICDownloadsDirectoryURL] path]));
86 std::string saveAsFilename =
87 base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]);
88 // It appears that the ImageCapture library adds an extension to the requested
89 // filename. Do that here to require a rename.
90 saveAsFilename += ".jpg";
91 base::FilePath toBeSaved = saveDir.Append(saveAsFilename);
92 ASSERT_EQ(static_cast<int>(strlen(kTestFileContents)),
93 base::WriteFile(toBeSaved, kTestFileContents,
94 strlen(kTestFileContents)));
96 NSMutableDictionary* returnOptions =
97 [NSMutableDictionary dictionaryWithDictionary:options];
98 [returnOptions setObject:base::SysUTF8ToNSString(saveAsFilename)
99 forKey:ICSavedFilename];
101 [static_cast<NSObject<ICCameraDeviceDownloadDelegate>*>(downloadDelegate)
104 options:returnOptions
105 contextInfo:contextInfo];
110 @interface MockMTPICCameraFile : ICCameraFile {
112 base::scoped_nsobject<NSString> name_;
113 base::scoped_nsobject<NSDate> date_;
116 - (id)init:(NSString*)name;
120 @implementation MockMTPICCameraFile
122 - (id)init:(NSString*)name {
123 if ((self = [super init])) {
124 name_.reset([name retain]);
125 date_.reset([[NSDate dateWithNaturalLanguageString:@"12/12/12"] retain]);
135 return base::mac::CFToNSCast(kUTTypeImage);
138 - (NSDate*)modificationDate {
142 - (NSDate*)creationDate {
152 class MTPDeviceDelegateImplMacTest : public testing::Test {
154 MTPDeviceDelegateImplMacTest() : camera_(NULL), delegate_(NULL) {}
156 virtual void SetUp() OVERRIDE {
157 ui_thread_.reset(new content::TestBrowserThread(
158 content::BrowserThread::UI, &message_loop_));
159 file_thread_.reset(new content::TestBrowserThread(
160 content::BrowserThread::FILE, &message_loop_));
161 io_thread_.reset(new content::TestBrowserThread(
162 content::BrowserThread::IO));
163 ASSERT_TRUE(io_thread_->Start());
165 storage_monitor::TestStorageMonitor* monitor =
166 storage_monitor::TestStorageMonitor::CreateAndInstall();
167 manager_.SetNotifications(monitor->receiver());
169 camera_ = [MockMTPICCameraDevice alloc];
170 id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
171 [delegate deviceBrowser:nil didAddDevice:camera_ moreComing:NO];
173 delegate_ = new MTPDeviceDelegateImplMac(kDeviceId, kDevicePath);
176 virtual void TearDown() OVERRIDE {
177 id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
178 [delegate deviceBrowser:nil didRemoveDevice:camera_ moreGoing:NO];
180 delegate_->CancelPendingTasksAndDeleteDelegate();
182 storage_monitor::TestStorageMonitor::Destroy();
187 void OnError(base::WaitableEvent* event, base::File::Error error) {
192 void OverlappedOnError(base::WaitableEvent* event,
193 base::File::Error error) {
194 overlapped_error_ = error;
198 void OnFileInfo(base::WaitableEvent* event,
199 const base::File::Info& info) {
200 error_ = base::File::FILE_OK;
205 void OnReadDir(base::WaitableEvent* event,
206 const storage::AsyncFileUtil::EntryList& files,
208 error_ = base::File::FILE_OK;
209 ASSERT_FALSE(has_more);
214 void OverlappedOnReadDir(base::WaitableEvent* event,
215 const storage::AsyncFileUtil::EntryList& files,
217 overlapped_error_ = base::File::FILE_OK;
218 ASSERT_FALSE(has_more);
219 overlapped_file_list_ = files;
223 void OnDownload(base::WaitableEvent* event,
224 const base::File::Info& file_info,
225 const base::FilePath& local_path) {
226 error_ = base::File::FILE_OK;
230 base::File::Error GetFileInfo(const base::FilePath& path,
231 base::File::Info* info) {
232 base::WaitableEvent wait(true, false);
233 delegate_->GetFileInfo(
235 base::Bind(&MTPDeviceDelegateImplMacTest::OnFileInfo,
236 base::Unretained(this),
238 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
239 base::Unretained(this),
243 EXPECT_TRUE(wait.IsSignaled());
248 base::File::Error ReadDir(const base::FilePath& path) {
249 base::WaitableEvent wait(true, false);
250 delegate_->ReadDirectory(
252 base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
253 base::Unretained(this),
255 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
256 base::Unretained(this),
264 base::File::Error DownloadFile(
265 const base::FilePath& path,
266 const base::FilePath& local_path) {
267 base::WaitableEvent wait(true, false);
268 delegate_->CreateSnapshotFile(
270 base::Bind(&MTPDeviceDelegateImplMacTest::OnDownload,
271 base::Unretained(this),
273 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
274 base::Unretained(this),
283 base::MessageLoopForUI message_loop_;
284 // Note: threads must be made in this order: UI > FILE > IO
285 scoped_ptr<content::TestBrowserThread> ui_thread_;
286 scoped_ptr<content::TestBrowserThread> file_thread_;
287 scoped_ptr<content::TestBrowserThread> io_thread_;
288 base::ScopedTempDir temp_dir_;
289 storage_monitor::ImageCaptureDeviceManager manager_;
290 MockMTPICCameraDevice* camera_;
292 // This object needs special deletion inside the above |task_runner_|.
293 MTPDeviceDelegateImplMac* delegate_;
295 base::File::Error error_;
296 base::File::Info info_;
297 storage::AsyncFileUtil::EntryList file_list_;
299 base::File::Error overlapped_error_;
300 storage::AsyncFileUtil::EntryList overlapped_file_list_;
303 DISALLOW_COPY_AND_ASSIGN(MTPDeviceDelegateImplMacTest);
306 TEST_F(MTPDeviceDelegateImplMacTest, TestGetRootFileInfo) {
307 base::File::Info info;
308 // Making a fresh delegate should have a single file entry for the synthetic
309 // root directory, with the name equal to the device id string.
310 EXPECT_EQ(base::File::FILE_OK,
311 GetFileInfo(base::FilePath(kDevicePath), &info));
312 EXPECT_TRUE(info.is_directory);
313 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
314 GetFileInfo(base::FilePath("/nonexistent"), &info));
316 // Signal the delegate that no files are coming.
317 delegate_->NoMoreItems();
319 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
320 EXPECT_EQ(0U, file_list_.size());
323 TEST_F(MTPDeviceDelegateImplMacTest, TestOverlappedReadDir) {
324 base::Time time1 = base::Time::Now();
325 base::File::Info info1;
327 info1.is_directory = false;
328 info1.is_symbolic_link = false;
329 info1.last_modified = time1;
330 info1.last_accessed = time1;
331 info1.creation_time = time1;
332 delegate_->ItemAdded("name1", info1);
334 base::WaitableEvent wait(true, false);
336 delegate_->ReadDirectory(
337 base::FilePath(kDevicePath),
338 base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
339 base::Unretained(this),
341 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
342 base::Unretained(this),
345 delegate_->ReadDirectory(
346 base::FilePath(kDevicePath),
347 base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnReadDir,
348 base::Unretained(this),
350 base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnError,
351 base::Unretained(this),
355 // Signal the delegate that no files are coming.
356 delegate_->NoMoreItems();
362 EXPECT_EQ(base::File::FILE_OK, error_);
363 EXPECT_EQ(1U, file_list_.size());
364 EXPECT_EQ(base::File::FILE_OK, overlapped_error_);
365 EXPECT_EQ(1U, overlapped_file_list_.size());
368 TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) {
369 base::Time time1 = base::Time::Now();
370 base::File::Info info1;
372 info1.is_directory = false;
373 info1.is_symbolic_link = false;
374 info1.last_modified = time1;
375 info1.last_accessed = time1;
376 info1.creation_time = time1;
377 delegate_->ItemAdded("name1", info1);
379 base::File::Info info;
380 EXPECT_EQ(base::File::FILE_OK,
381 GetFileInfo(base::FilePath("/ic:id/name1"), &info));
382 EXPECT_EQ(info1.size, info.size);
383 EXPECT_EQ(info1.is_directory, info.is_directory);
384 EXPECT_EQ(info1.last_modified, info.last_modified);
385 EXPECT_EQ(info1.last_accessed, info.last_accessed);
386 EXPECT_EQ(info1.creation_time, info.creation_time);
389 delegate_->ItemAdded("name2", info1);
390 delegate_->NoMoreItems();
392 EXPECT_EQ(base::File::FILE_OK,
393 GetFileInfo(base::FilePath("/ic:id/name2"), &info));
394 EXPECT_EQ(info1.size, info.size);
396 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
398 ASSERT_EQ(2U, file_list_.size());
399 EXPECT_EQ(time1, file_list_[0].last_modified_time);
400 EXPECT_FALSE(file_list_[0].is_directory);
401 EXPECT_EQ("name1", file_list_[0].name);
403 EXPECT_EQ(time1, file_list_[1].last_modified_time);
404 EXPECT_FALSE(file_list_[1].is_directory);
405 EXPECT_EQ("name2", file_list_[1].name);
408 TEST_F(MTPDeviceDelegateImplMacTest, TestDirectoriesAndSorting) {
409 base::Time time1 = base::Time::Now();
410 base::File::Info info1;
412 info1.is_directory = false;
413 info1.is_symbolic_link = false;
414 info1.last_modified = time1;
415 info1.last_accessed = time1;
416 info1.creation_time = time1;
417 delegate_->ItemAdded("name2", info1);
419 info1.is_directory = true;
420 delegate_->ItemAdded("dir2", info1);
421 delegate_->ItemAdded("dir1", info1);
423 info1.is_directory = false;
424 delegate_->ItemAdded("name1", info1);
425 delegate_->NoMoreItems();
427 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
429 ASSERT_EQ(4U, file_list_.size());
430 EXPECT_EQ("dir1", file_list_[0].name);
431 EXPECT_EQ("dir2", file_list_[1].name);
432 EXPECT_EQ(time1, file_list_[2].last_modified_time);
433 EXPECT_FALSE(file_list_[2].is_directory);
434 EXPECT_EQ("name1", file_list_[2].name);
436 EXPECT_EQ(time1, file_list_[3].last_modified_time);
437 EXPECT_FALSE(file_list_[3].is_directory);
438 EXPECT_EQ("name2", file_list_[3].name);
441 TEST_F(MTPDeviceDelegateImplMacTest, SubDirectories) {
442 base::Time time1 = base::Time::Now();
443 base::File::Info info1;
445 info1.is_directory = true;
446 info1.is_symbolic_link = false;
447 info1.last_modified = time1;
448 info1.last_accessed = time1;
449 info1.creation_time = time1;
450 delegate_->ItemAdded("dir1", info1);
453 info1.is_directory = false;
454 info1.is_symbolic_link = false;
455 info1.last_modified = time1;
456 info1.last_accessed = time1;
457 info1.creation_time = time1;
458 delegate_->ItemAdded("dir1/name1", info1);
460 info1.is_directory = true;
462 delegate_->ItemAdded("dir2", info1);
464 info1.is_directory = false;
466 delegate_->ItemAdded("dir2/name2", info1);
468 info1.is_directory = true;
470 delegate_->ItemAdded("dir2/subdir", info1);
472 info1.is_directory = false;
474 delegate_->ItemAdded("dir2/subdir/name3", info1);
475 delegate_->ItemAdded("name4", info1);
477 delegate_->NoMoreItems();
479 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
480 ASSERT_EQ(3U, file_list_.size());
481 EXPECT_TRUE(file_list_[0].is_directory);
482 EXPECT_EQ("dir1", file_list_[0].name);
483 EXPECT_TRUE(file_list_[1].is_directory);
484 EXPECT_EQ("dir2", file_list_[1].name);
485 EXPECT_FALSE(file_list_[2].is_directory);
486 EXPECT_EQ("name4", file_list_[2].name);
488 EXPECT_EQ(base::File::FILE_OK,
489 ReadDir(base::FilePath(kDevicePath).Append("dir1")));
490 ASSERT_EQ(1U, file_list_.size());
491 EXPECT_FALSE(file_list_[0].is_directory);
492 EXPECT_EQ("name1", file_list_[0].name);
494 EXPECT_EQ(base::File::FILE_OK,
495 ReadDir(base::FilePath(kDevicePath).Append("dir2")));
496 ASSERT_EQ(2U, file_list_.size());
497 EXPECT_FALSE(file_list_[0].is_directory);
498 EXPECT_EQ("name2", file_list_[0].name);
499 EXPECT_TRUE(file_list_[1].is_directory);
500 EXPECT_EQ("subdir", file_list_[1].name);
502 EXPECT_EQ(base::File::FILE_OK,
503 ReadDir(base::FilePath(kDevicePath)
504 .Append("dir2").Append("subdir")));
505 ASSERT_EQ(1U, file_list_.size());
506 EXPECT_FALSE(file_list_[0].is_directory);
507 EXPECT_EQ("name3", file_list_[0].name);
509 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
510 ReadDir(base::FilePath(kDevicePath)
511 .Append("dir2").Append("subdir").Append("subdir")));
512 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
513 ReadDir(base::FilePath(kDevicePath)
514 .Append("dir3").Append("subdir")));
515 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
516 ReadDir(base::FilePath(kDevicePath).Append("dir3")));
519 TEST_F(MTPDeviceDelegateImplMacTest, TestDownload) {
520 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
521 base::Time t1 = base::Time::Now();
522 base::File::Info info;
524 info.is_directory = false;
525 info.is_symbolic_link = false;
526 info.last_modified = t1;
527 info.last_accessed = t1;
528 info.creation_time = t1;
529 std::string kTestFileName("filename");
530 base::scoped_nsobject<MockMTPICCameraFile> picture1(
531 [[MockMTPICCameraFile alloc]
532 init:base::SysUTF8ToNSString(kTestFileName)]);
533 [camera_ addMediaFile:picture1];
534 delegate_->ItemAdded(kTestFileName, info);
535 delegate_->NoMoreItems();
537 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
538 ASSERT_EQ(1U, file_list_.size());
539 ASSERT_EQ("filename", file_list_[0].name);
541 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
542 DownloadFile(base::FilePath("/ic:id/nonexist"),
543 temp_dir_.path().Append("target")));
545 EXPECT_EQ(base::File::FILE_OK,
546 DownloadFile(base::FilePath("/ic:id/filename"),
547 temp_dir_.path().Append("target")));
548 std::string contents;
549 EXPECT_TRUE(base::ReadFileToString(temp_dir_.path().Append("target"),
551 EXPECT_EQ(kTestFileContents, contents);