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/file_util.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/mac/cocoa_protocols.h"
11 #include "base/mac/foundation_util.h"
12 #include "base/mac/scoped_nsobject.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/run_loop.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/synchronization/waitable_event.h"
17 #include "base/test/sequenced_worker_pool_owner.h"
18 #include "base/threading/sequenced_worker_pool.h"
19 #include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h"
20 #include "chrome/browser/storage_monitor/image_capture_device_manager.h"
21 #include "chrome/browser/storage_monitor/test_storage_monitor.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/test/test_browser_thread.h"
24 #include "testing/gtest/include/gtest/gtest.h"
26 #if !defined(MAC_OS_X_VERSION_10_7) || \
27 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
29 @interface NSObject (ICCameraDeviceDelegateLionAPI)
30 - (void)deviceDidBecomeReadyWithCompleteContentCatalog:(ICDevice*)device;
31 - (void)didDownloadFile:(ICCameraFile*)file
33 options:(NSDictionary*)options
34 contextInfo:(void*)contextInfo;
41 const char kDeviceId[] = "id";
42 const char kDevicePath[] = "/ic:id";
43 const char kTestFileContents[] = "test";
47 @interface MockMTPICCameraDevice : ICCameraDevice {
49 base::scoped_nsobject<NSMutableArray> allMediaFiles_;
52 - (void)addMediaFile:(ICCameraFile*)file;
56 @implementation MockMTPICCameraDevice
58 - (NSString*)mountPoint {
66 - (NSString*)UUIDString {
67 return base::SysUTF8ToNSString(kDeviceId);
70 - (ICDeviceType)type {
71 return ICDeviceTypeCamera;
74 - (void)requestOpenSession {
77 - (void)requestCloseSession {
80 - (NSArray*)mediaFiles {
81 return allMediaFiles_;
84 - (void)addMediaFile:(ICCameraFile*)file {
85 if (!allMediaFiles_.get())
86 allMediaFiles_.reset([[NSMutableArray alloc] init]);
87 [allMediaFiles_ addObject:file];
90 - (void)requestDownloadFile:(ICCameraFile*)file
91 options:(NSDictionary*)options
92 downloadDelegate:(id<ICCameraDeviceDownloadDelegate>)downloadDelegate
93 didDownloadSelector:(SEL)selector
94 contextInfo:(void*)contextInfo {
95 base::FilePath saveDir(base::SysNSStringToUTF8(
96 [[options objectForKey:ICDownloadsDirectoryURL] path]));
97 std::string saveAsFilename =
98 base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]);
99 // It appears that the ImageCapture library adds an extension to the requested
100 // filename. Do that here to require a rename.
101 saveAsFilename += ".jpg";
102 base::FilePath toBeSaved = saveDir.Append(saveAsFilename);
103 ASSERT_EQ(static_cast<int>(strlen(kTestFileContents)),
104 file_util::WriteFile(toBeSaved, kTestFileContents,
105 strlen(kTestFileContents)));
107 NSMutableDictionary* returnOptions =
108 [NSMutableDictionary dictionaryWithDictionary:options];
109 [returnOptions setObject:base::SysUTF8ToNSString(saveAsFilename)
110 forKey:ICSavedFilename];
112 [static_cast<NSObject<ICCameraDeviceDownloadDelegate>*>(downloadDelegate)
115 options:returnOptions
116 contextInfo:contextInfo];
121 @interface MockMTPICCameraFile : ICCameraFile {
123 base::scoped_nsobject<NSString> name_;
124 base::scoped_nsobject<NSDate> date_;
127 - (id)init:(NSString*)name;
131 @implementation MockMTPICCameraFile
133 - (id)init:(NSString*)name {
134 if ((self = [super init])) {
135 name_.reset([name retain]);
136 date_.reset([[NSDate dateWithNaturalLanguageString:@"12/12/12"] retain]);
146 return base::mac::CFToNSCast(kUTTypeImage);
149 - (NSDate*)modificationDate {
153 - (NSDate*)creationDate {
163 class MTPDeviceDelegateImplMacTest : public testing::Test {
165 MTPDeviceDelegateImplMacTest() : camera_(NULL), delegate_(NULL) {}
167 virtual void SetUp() OVERRIDE {
168 ui_thread_.reset(new content::TestBrowserThread(
169 content::BrowserThread::UI, &message_loop_));
170 file_thread_.reset(new content::TestBrowserThread(
171 content::BrowserThread::FILE, &message_loop_));
172 io_thread_.reset(new content::TestBrowserThread(
173 content::BrowserThread::IO));
174 ASSERT_TRUE(io_thread_->Start());
176 TestStorageMonitor* monitor = TestStorageMonitor::CreateAndInstall();
177 manager_.SetNotifications(monitor->receiver());
179 camera_ = [MockMTPICCameraDevice alloc];
180 id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
181 [delegate deviceBrowser:nil didAddDevice:camera_ moreComing:NO];
183 delegate_ = new MTPDeviceDelegateImplMac(kDeviceId, kDevicePath);
186 virtual void TearDown() OVERRIDE {
187 id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
188 [delegate deviceBrowser:nil didRemoveDevice:camera_ moreGoing:NO];
190 delegate_->CancelPendingTasksAndDeleteDelegate();
192 TestStorageMonitor::RemoveSingleton();
197 void OnError(base::WaitableEvent* event, base::PlatformFileError error) {
202 void OverlappedOnError(base::WaitableEvent* event,
203 base::PlatformFileError error) {
204 overlapped_error_ = error;
208 void OnFileInfo(base::WaitableEvent* event,
209 const base::PlatformFileInfo& info) {
210 error_ = base::PLATFORM_FILE_OK;
215 void OnReadDir(base::WaitableEvent* event,
216 const fileapi::AsyncFileUtil::EntryList& files,
218 error_ = base::PLATFORM_FILE_OK;
219 ASSERT_FALSE(has_more);
224 void OverlappedOnReadDir(base::WaitableEvent* event,
225 const fileapi::AsyncFileUtil::EntryList& files,
227 overlapped_error_ = base::PLATFORM_FILE_OK;
228 ASSERT_FALSE(has_more);
229 overlapped_file_list_ = files;
233 void OnDownload(base::WaitableEvent* event,
234 const base::PlatformFileInfo& file_info,
235 const base::FilePath& local_path) {
236 error_ = base::PLATFORM_FILE_OK;
240 base::PlatformFileError GetFileInfo(const base::FilePath& path,
241 base::PlatformFileInfo* info) {
242 base::WaitableEvent wait(true, false);
243 delegate_->GetFileInfo(
245 base::Bind(&MTPDeviceDelegateImplMacTest::OnFileInfo,
246 base::Unretained(this),
248 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
249 base::Unretained(this),
253 EXPECT_TRUE(wait.IsSignaled());
258 base::PlatformFileError ReadDir(const base::FilePath& path) {
259 base::WaitableEvent wait(true, false);
260 delegate_->ReadDirectory(
262 base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
263 base::Unretained(this),
265 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
266 base::Unretained(this),
274 base::PlatformFileError DownloadFile(
275 const base::FilePath& path,
276 const base::FilePath& local_path) {
277 base::WaitableEvent wait(true, false);
278 delegate_->CreateSnapshotFile(
280 base::Bind(&MTPDeviceDelegateImplMacTest::OnDownload,
281 base::Unretained(this),
283 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
284 base::Unretained(this),
293 base::MessageLoopForUI message_loop_;
294 // Note: threads must be made in this order: UI > FILE > IO
295 scoped_ptr<content::TestBrowserThread> ui_thread_;
296 scoped_ptr<content::TestBrowserThread> file_thread_;
297 scoped_ptr<content::TestBrowserThread> io_thread_;
298 base::ScopedTempDir temp_dir_;
299 ImageCaptureDeviceManager manager_;
300 MockMTPICCameraDevice* camera_;
302 // This object needs special deletion inside the above |task_runner_|.
303 MTPDeviceDelegateImplMac* delegate_;
305 base::PlatformFileError error_;
306 base::PlatformFileInfo info_;
307 fileapi::AsyncFileUtil::EntryList file_list_;
309 base::PlatformFileError overlapped_error_;
310 fileapi::AsyncFileUtil::EntryList overlapped_file_list_;
313 DISALLOW_COPY_AND_ASSIGN(MTPDeviceDelegateImplMacTest);
316 TEST_F(MTPDeviceDelegateImplMacTest, TestGetRootFileInfo) {
317 base::PlatformFileInfo info;
318 // Making a fresh delegate should have a single file entry for the synthetic
319 // root directory, with the name equal to the device id string.
320 EXPECT_EQ(base::PLATFORM_FILE_OK,
321 GetFileInfo(base::FilePath(kDevicePath), &info));
322 EXPECT_TRUE(info.is_directory);
323 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
324 GetFileInfo(base::FilePath("/nonexistent"), &info));
326 // Signal the delegate that no files are coming.
327 delegate_->NoMoreItems();
329 EXPECT_EQ(base::PLATFORM_FILE_OK, ReadDir(base::FilePath(kDevicePath)));
330 EXPECT_EQ(0U, file_list_.size());
333 TEST_F(MTPDeviceDelegateImplMacTest, TestOverlappedReadDir) {
334 base::Time time1 = base::Time::Now();
335 base::PlatformFileInfo info1;
337 info1.is_directory = false;
338 info1.is_symbolic_link = false;
339 info1.last_modified = time1;
340 info1.last_accessed = time1;
341 info1.creation_time = time1;
342 delegate_->ItemAdded("name1", info1);
344 base::WaitableEvent wait(true, false);
346 delegate_->ReadDirectory(
347 base::FilePath(kDevicePath),
348 base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
349 base::Unretained(this),
351 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
352 base::Unretained(this),
355 delegate_->ReadDirectory(
356 base::FilePath(kDevicePath),
357 base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnReadDir,
358 base::Unretained(this),
360 base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnError,
361 base::Unretained(this),
365 // Signal the delegate that no files are coming.
366 delegate_->NoMoreItems();
372 EXPECT_EQ(base::PLATFORM_FILE_OK, error_);
373 EXPECT_EQ(1U, file_list_.size());
374 EXPECT_EQ(base::PLATFORM_FILE_OK, overlapped_error_);
375 EXPECT_EQ(1U, overlapped_file_list_.size());
378 TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) {
379 base::Time time1 = base::Time::Now();
380 base::PlatformFileInfo info1;
382 info1.is_directory = false;
383 info1.is_symbolic_link = false;
384 info1.last_modified = time1;
385 info1.last_accessed = time1;
386 info1.creation_time = time1;
387 delegate_->ItemAdded("name1", info1);
389 base::PlatformFileInfo info;
390 EXPECT_EQ(base::PLATFORM_FILE_OK,
391 GetFileInfo(base::FilePath("/ic:id/name1"), &info));
392 EXPECT_EQ(info1.size, info.size);
393 EXPECT_EQ(info1.is_directory, info.is_directory);
394 EXPECT_EQ(info1.last_modified, info.last_modified);
395 EXPECT_EQ(info1.last_accessed, info.last_accessed);
396 EXPECT_EQ(info1.creation_time, info.creation_time);
399 delegate_->ItemAdded("name2", info1);
400 delegate_->NoMoreItems();
402 EXPECT_EQ(base::PLATFORM_FILE_OK,
403 GetFileInfo(base::FilePath("/ic:id/name2"), &info));
404 EXPECT_EQ(info1.size, info.size);
406 EXPECT_EQ(base::PLATFORM_FILE_OK, ReadDir(base::FilePath(kDevicePath)));
408 ASSERT_EQ(2U, file_list_.size());
409 EXPECT_EQ(time1, file_list_[0].last_modified_time);
410 EXPECT_FALSE(file_list_[0].is_directory);
411 EXPECT_EQ("name1", file_list_[0].name);
413 EXPECT_EQ(time1, file_list_[1].last_modified_time);
414 EXPECT_FALSE(file_list_[1].is_directory);
415 EXPECT_EQ("name2", file_list_[1].name);
418 TEST_F(MTPDeviceDelegateImplMacTest, TestDirectoriesAndSorting) {
419 base::Time time1 = base::Time::Now();
420 base::PlatformFileInfo info1;
422 info1.is_directory = false;
423 info1.is_symbolic_link = false;
424 info1.last_modified = time1;
425 info1.last_accessed = time1;
426 info1.creation_time = time1;
427 delegate_->ItemAdded("name2", info1);
429 info1.is_directory = true;
430 delegate_->ItemAdded("dir2", info1);
431 delegate_->ItemAdded("dir1", info1);
433 info1.is_directory = false;
434 delegate_->ItemAdded("name1", info1);
435 delegate_->NoMoreItems();
437 EXPECT_EQ(base::PLATFORM_FILE_OK, ReadDir(base::FilePath(kDevicePath)));
439 ASSERT_EQ(4U, file_list_.size());
440 EXPECT_EQ("dir1", file_list_[0].name);
441 EXPECT_EQ("dir2", file_list_[1].name);
442 EXPECT_EQ(time1, file_list_[2].last_modified_time);
443 EXPECT_FALSE(file_list_[2].is_directory);
444 EXPECT_EQ("name1", file_list_[2].name);
446 EXPECT_EQ(time1, file_list_[3].last_modified_time);
447 EXPECT_FALSE(file_list_[3].is_directory);
448 EXPECT_EQ("name2", file_list_[3].name);
451 TEST_F(MTPDeviceDelegateImplMacTest, SubDirectories) {
452 base::Time time1 = base::Time::Now();
453 base::PlatformFileInfo info1;
455 info1.is_directory = true;
456 info1.is_symbolic_link = false;
457 info1.last_modified = time1;
458 info1.last_accessed = time1;
459 info1.creation_time = time1;
460 delegate_->ItemAdded("dir1", info1);
463 info1.is_directory = false;
464 info1.is_symbolic_link = false;
465 info1.last_modified = time1;
466 info1.last_accessed = time1;
467 info1.creation_time = time1;
468 delegate_->ItemAdded("dir1/name1", info1);
470 info1.is_directory = true;
472 delegate_->ItemAdded("dir2", info1);
474 info1.is_directory = false;
476 delegate_->ItemAdded("dir2/name2", info1);
478 info1.is_directory = true;
480 delegate_->ItemAdded("dir2/subdir", info1);
482 info1.is_directory = false;
484 delegate_->ItemAdded("dir2/subdir/name3", info1);
485 delegate_->ItemAdded("name4", info1);
487 delegate_->NoMoreItems();
489 EXPECT_EQ(base::PLATFORM_FILE_OK, ReadDir(base::FilePath(kDevicePath)));
490 ASSERT_EQ(3U, file_list_.size());
491 EXPECT_TRUE(file_list_[0].is_directory);
492 EXPECT_EQ("dir1", file_list_[0].name);
493 EXPECT_TRUE(file_list_[1].is_directory);
494 EXPECT_EQ("dir2", file_list_[1].name);
495 EXPECT_FALSE(file_list_[2].is_directory);
496 EXPECT_EQ("name4", file_list_[2].name);
498 EXPECT_EQ(base::PLATFORM_FILE_OK,
499 ReadDir(base::FilePath(kDevicePath).Append("dir1")));
500 ASSERT_EQ(1U, file_list_.size());
501 EXPECT_FALSE(file_list_[0].is_directory);
502 EXPECT_EQ("name1", file_list_[0].name);
504 EXPECT_EQ(base::PLATFORM_FILE_OK,
505 ReadDir(base::FilePath(kDevicePath).Append("dir2")));
506 ASSERT_EQ(2U, file_list_.size());
507 EXPECT_FALSE(file_list_[0].is_directory);
508 EXPECT_EQ("name2", file_list_[0].name);
509 EXPECT_TRUE(file_list_[1].is_directory);
510 EXPECT_EQ("subdir", file_list_[1].name);
512 EXPECT_EQ(base::PLATFORM_FILE_OK,
513 ReadDir(base::FilePath(kDevicePath)
514 .Append("dir2").Append("subdir")));
515 ASSERT_EQ(1U, file_list_.size());
516 EXPECT_FALSE(file_list_[0].is_directory);
517 EXPECT_EQ("name3", file_list_[0].name);
519 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
520 ReadDir(base::FilePath(kDevicePath)
521 .Append("dir2").Append("subdir").Append("subdir")));
522 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
523 ReadDir(base::FilePath(kDevicePath)
524 .Append("dir3").Append("subdir")));
525 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
526 ReadDir(base::FilePath(kDevicePath).Append("dir3")));
529 TEST_F(MTPDeviceDelegateImplMacTest, TestDownload) {
530 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
531 base::Time t1 = base::Time::Now();
532 base::PlatformFileInfo info;
534 info.is_directory = false;
535 info.is_symbolic_link = false;
536 info.last_modified = t1;
537 info.last_accessed = t1;
538 info.creation_time = t1;
539 std::string kTestFileName("filename");
540 base::scoped_nsobject<MockMTPICCameraFile> picture1(
541 [[MockMTPICCameraFile alloc]
542 init:base::SysUTF8ToNSString(kTestFileName)]);
543 [camera_ addMediaFile:picture1];
544 delegate_->ItemAdded(kTestFileName, info);
545 delegate_->NoMoreItems();
547 EXPECT_EQ(base::PLATFORM_FILE_OK, ReadDir(base::FilePath(kDevicePath)));
548 ASSERT_EQ(1U, file_list_.size());
549 ASSERT_EQ("filename", file_list_[0].name);
551 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
552 DownloadFile(base::FilePath("/ic:id/nonexist"),
553 temp_dir_.path().Append("target")));
555 EXPECT_EQ(base::PLATFORM_FILE_OK,
556 DownloadFile(base::FilePath("/ic:id/filename"),
557 temp_dir_.path().Append("target")));
558 std::string contents;
559 EXPECT_TRUE(base::ReadFileToString(temp_dir_.path().Append("target"),
561 EXPECT_EQ(kTestFileContents, contents);