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/file.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/message_loop/message_loop.h"
15 #include "base/run_loop.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/synchronization/waitable_event.h"
18 #include "base/test/sequenced_worker_pool_owner.h"
19 #include "base/threading/sequenced_worker_pool.h"
20 #include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h"
21 #include "chrome/browser/storage_monitor/image_capture_device_manager.h"
22 #include "chrome/browser/storage_monitor/test_storage_monitor.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/test/test_browser_thread.h"
25 #include "testing/gtest/include/gtest/gtest.h"
27 #if !defined(MAC_OS_X_VERSION_10_7) || \
28 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
30 @interface NSObject (ICCameraDeviceDelegateLionAPI)
31 - (void)deviceDidBecomeReadyWithCompleteContentCatalog:(ICDevice*)device;
32 - (void)didDownloadFile:(ICCameraFile*)file
34 options:(NSDictionary*)options
35 contextInfo:(void*)contextInfo;
42 const char kDeviceId[] = "id";
43 const char kDevicePath[] = "/ic:id";
44 const char kTestFileContents[] = "test";
48 @interface MockMTPICCameraDevice : ICCameraDevice {
50 base::scoped_nsobject<NSMutableArray> allMediaFiles_;
53 - (void)addMediaFile:(ICCameraFile*)file;
57 @implementation MockMTPICCameraDevice
59 - (NSString*)mountPoint {
67 - (NSString*)UUIDString {
68 return base::SysUTF8ToNSString(kDeviceId);
71 - (ICDeviceType)type {
72 return ICDeviceTypeCamera;
75 - (void)requestOpenSession {
78 - (void)requestCloseSession {
81 - (NSArray*)mediaFiles {
82 return allMediaFiles_;
85 - (void)addMediaFile:(ICCameraFile*)file {
86 if (!allMediaFiles_.get())
87 allMediaFiles_.reset([[NSMutableArray alloc] init]);
88 [allMediaFiles_ addObject:file];
91 - (void)requestDownloadFile:(ICCameraFile*)file
92 options:(NSDictionary*)options
93 downloadDelegate:(id<ICCameraDeviceDownloadDelegate>)downloadDelegate
94 didDownloadSelector:(SEL)selector
95 contextInfo:(void*)contextInfo {
96 base::FilePath saveDir(base::SysNSStringToUTF8(
97 [[options objectForKey:ICDownloadsDirectoryURL] path]));
98 std::string saveAsFilename =
99 base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]);
100 // It appears that the ImageCapture library adds an extension to the requested
101 // filename. Do that here to require a rename.
102 saveAsFilename += ".jpg";
103 base::FilePath toBeSaved = saveDir.Append(saveAsFilename);
104 ASSERT_EQ(static_cast<int>(strlen(kTestFileContents)),
105 file_util::WriteFile(toBeSaved, kTestFileContents,
106 strlen(kTestFileContents)));
108 NSMutableDictionary* returnOptions =
109 [NSMutableDictionary dictionaryWithDictionary:options];
110 [returnOptions setObject:base::SysUTF8ToNSString(saveAsFilename)
111 forKey:ICSavedFilename];
113 [static_cast<NSObject<ICCameraDeviceDownloadDelegate>*>(downloadDelegate)
116 options:returnOptions
117 contextInfo:contextInfo];
122 @interface MockMTPICCameraFile : ICCameraFile {
124 base::scoped_nsobject<NSString> name_;
125 base::scoped_nsobject<NSDate> date_;
128 - (id)init:(NSString*)name;
132 @implementation MockMTPICCameraFile
134 - (id)init:(NSString*)name {
135 if ((self = [super init])) {
136 name_.reset([name retain]);
137 date_.reset([[NSDate dateWithNaturalLanguageString:@"12/12/12"] retain]);
147 return base::mac::CFToNSCast(kUTTypeImage);
150 - (NSDate*)modificationDate {
154 - (NSDate*)creationDate {
164 class MTPDeviceDelegateImplMacTest : public testing::Test {
166 MTPDeviceDelegateImplMacTest() : camera_(NULL), delegate_(NULL) {}
168 virtual void SetUp() OVERRIDE {
169 ui_thread_.reset(new content::TestBrowserThread(
170 content::BrowserThread::UI, &message_loop_));
171 file_thread_.reset(new content::TestBrowserThread(
172 content::BrowserThread::FILE, &message_loop_));
173 io_thread_.reset(new content::TestBrowserThread(
174 content::BrowserThread::IO));
175 ASSERT_TRUE(io_thread_->Start());
177 TestStorageMonitor* monitor = TestStorageMonitor::CreateAndInstall();
178 manager_.SetNotifications(monitor->receiver());
180 camera_ = [MockMTPICCameraDevice alloc];
181 id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
182 [delegate deviceBrowser:nil didAddDevice:camera_ moreComing:NO];
184 delegate_ = new MTPDeviceDelegateImplMac(kDeviceId, kDevicePath);
187 virtual void TearDown() OVERRIDE {
188 id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
189 [delegate deviceBrowser:nil didRemoveDevice:camera_ moreGoing:NO];
191 delegate_->CancelPendingTasksAndDeleteDelegate();
193 TestStorageMonitor::RemoveSingleton();
198 void OnError(base::WaitableEvent* event, base::File::Error error) {
203 void OverlappedOnError(base::WaitableEvent* event,
204 base::File::Error error) {
205 overlapped_error_ = error;
209 void OnFileInfo(base::WaitableEvent* event,
210 const base::File::Info& info) {
211 error_ = base::File::FILE_OK;
216 void OnReadDir(base::WaitableEvent* event,
217 const fileapi::AsyncFileUtil::EntryList& files,
219 error_ = base::File::FILE_OK;
220 ASSERT_FALSE(has_more);
225 void OverlappedOnReadDir(base::WaitableEvent* event,
226 const fileapi::AsyncFileUtil::EntryList& files,
228 overlapped_error_ = base::File::FILE_OK;
229 ASSERT_FALSE(has_more);
230 overlapped_file_list_ = files;
234 void OnDownload(base::WaitableEvent* event,
235 const base::File::Info& file_info,
236 const base::FilePath& local_path) {
237 error_ = base::File::FILE_OK;
241 base::File::Error GetFileInfo(const base::FilePath& path,
242 base::File::Info* info) {
243 base::WaitableEvent wait(true, false);
244 delegate_->GetFileInfo(
246 base::Bind(&MTPDeviceDelegateImplMacTest::OnFileInfo,
247 base::Unretained(this),
249 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
250 base::Unretained(this),
254 EXPECT_TRUE(wait.IsSignaled());
259 base::File::Error ReadDir(const base::FilePath& path) {
260 base::WaitableEvent wait(true, false);
261 delegate_->ReadDirectory(
263 base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
264 base::Unretained(this),
266 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
267 base::Unretained(this),
275 base::File::Error DownloadFile(
276 const base::FilePath& path,
277 const base::FilePath& local_path) {
278 base::WaitableEvent wait(true, false);
279 delegate_->CreateSnapshotFile(
281 base::Bind(&MTPDeviceDelegateImplMacTest::OnDownload,
282 base::Unretained(this),
284 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
285 base::Unretained(this),
294 base::MessageLoopForUI message_loop_;
295 // Note: threads must be made in this order: UI > FILE > IO
296 scoped_ptr<content::TestBrowserThread> ui_thread_;
297 scoped_ptr<content::TestBrowserThread> file_thread_;
298 scoped_ptr<content::TestBrowserThread> io_thread_;
299 base::ScopedTempDir temp_dir_;
300 ImageCaptureDeviceManager manager_;
301 MockMTPICCameraDevice* camera_;
303 // This object needs special deletion inside the above |task_runner_|.
304 MTPDeviceDelegateImplMac* delegate_;
306 base::File::Error error_;
307 base::File::Info info_;
308 fileapi::AsyncFileUtil::EntryList file_list_;
310 base::File::Error overlapped_error_;
311 fileapi::AsyncFileUtil::EntryList overlapped_file_list_;
314 DISALLOW_COPY_AND_ASSIGN(MTPDeviceDelegateImplMacTest);
317 TEST_F(MTPDeviceDelegateImplMacTest, TestGetRootFileInfo) {
318 base::File::Info info;
319 // Making a fresh delegate should have a single file entry for the synthetic
320 // root directory, with the name equal to the device id string.
321 EXPECT_EQ(base::File::FILE_OK,
322 GetFileInfo(base::FilePath(kDevicePath), &info));
323 EXPECT_TRUE(info.is_directory);
324 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
325 GetFileInfo(base::FilePath("/nonexistent"), &info));
327 // Signal the delegate that no files are coming.
328 delegate_->NoMoreItems();
330 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
331 EXPECT_EQ(0U, file_list_.size());
334 TEST_F(MTPDeviceDelegateImplMacTest, TestOverlappedReadDir) {
335 base::Time time1 = base::Time::Now();
336 base::File::Info info1;
338 info1.is_directory = false;
339 info1.is_symbolic_link = false;
340 info1.last_modified = time1;
341 info1.last_accessed = time1;
342 info1.creation_time = time1;
343 delegate_->ItemAdded("name1", info1);
345 base::WaitableEvent wait(true, false);
347 delegate_->ReadDirectory(
348 base::FilePath(kDevicePath),
349 base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
350 base::Unretained(this),
352 base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
353 base::Unretained(this),
356 delegate_->ReadDirectory(
357 base::FilePath(kDevicePath),
358 base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnReadDir,
359 base::Unretained(this),
361 base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnError,
362 base::Unretained(this),
366 // Signal the delegate that no files are coming.
367 delegate_->NoMoreItems();
373 EXPECT_EQ(base::File::FILE_OK, error_);
374 EXPECT_EQ(1U, file_list_.size());
375 EXPECT_EQ(base::File::FILE_OK, overlapped_error_);
376 EXPECT_EQ(1U, overlapped_file_list_.size());
379 TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) {
380 base::Time time1 = base::Time::Now();
381 base::File::Info info1;
383 info1.is_directory = false;
384 info1.is_symbolic_link = false;
385 info1.last_modified = time1;
386 info1.last_accessed = time1;
387 info1.creation_time = time1;
388 delegate_->ItemAdded("name1", info1);
390 base::File::Info info;
391 EXPECT_EQ(base::File::FILE_OK,
392 GetFileInfo(base::FilePath("/ic:id/name1"), &info));
393 EXPECT_EQ(info1.size, info.size);
394 EXPECT_EQ(info1.is_directory, info.is_directory);
395 EXPECT_EQ(info1.last_modified, info.last_modified);
396 EXPECT_EQ(info1.last_accessed, info.last_accessed);
397 EXPECT_EQ(info1.creation_time, info.creation_time);
400 delegate_->ItemAdded("name2", info1);
401 delegate_->NoMoreItems();
403 EXPECT_EQ(base::File::FILE_OK,
404 GetFileInfo(base::FilePath("/ic:id/name2"), &info));
405 EXPECT_EQ(info1.size, info.size);
407 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
409 ASSERT_EQ(2U, file_list_.size());
410 EXPECT_EQ(time1, file_list_[0].last_modified_time);
411 EXPECT_FALSE(file_list_[0].is_directory);
412 EXPECT_EQ("name1", file_list_[0].name);
414 EXPECT_EQ(time1, file_list_[1].last_modified_time);
415 EXPECT_FALSE(file_list_[1].is_directory);
416 EXPECT_EQ("name2", file_list_[1].name);
419 TEST_F(MTPDeviceDelegateImplMacTest, TestDirectoriesAndSorting) {
420 base::Time time1 = base::Time::Now();
421 base::File::Info info1;
423 info1.is_directory = false;
424 info1.is_symbolic_link = false;
425 info1.last_modified = time1;
426 info1.last_accessed = time1;
427 info1.creation_time = time1;
428 delegate_->ItemAdded("name2", info1);
430 info1.is_directory = true;
431 delegate_->ItemAdded("dir2", info1);
432 delegate_->ItemAdded("dir1", info1);
434 info1.is_directory = false;
435 delegate_->ItemAdded("name1", info1);
436 delegate_->NoMoreItems();
438 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
440 ASSERT_EQ(4U, file_list_.size());
441 EXPECT_EQ("dir1", file_list_[0].name);
442 EXPECT_EQ("dir2", file_list_[1].name);
443 EXPECT_EQ(time1, file_list_[2].last_modified_time);
444 EXPECT_FALSE(file_list_[2].is_directory);
445 EXPECT_EQ("name1", file_list_[2].name);
447 EXPECT_EQ(time1, file_list_[3].last_modified_time);
448 EXPECT_FALSE(file_list_[3].is_directory);
449 EXPECT_EQ("name2", file_list_[3].name);
452 TEST_F(MTPDeviceDelegateImplMacTest, SubDirectories) {
453 base::Time time1 = base::Time::Now();
454 base::File::Info info1;
456 info1.is_directory = true;
457 info1.is_symbolic_link = false;
458 info1.last_modified = time1;
459 info1.last_accessed = time1;
460 info1.creation_time = time1;
461 delegate_->ItemAdded("dir1", info1);
464 info1.is_directory = false;
465 info1.is_symbolic_link = false;
466 info1.last_modified = time1;
467 info1.last_accessed = time1;
468 info1.creation_time = time1;
469 delegate_->ItemAdded("dir1/name1", info1);
471 info1.is_directory = true;
473 delegate_->ItemAdded("dir2", info1);
475 info1.is_directory = false;
477 delegate_->ItemAdded("dir2/name2", info1);
479 info1.is_directory = true;
481 delegate_->ItemAdded("dir2/subdir", info1);
483 info1.is_directory = false;
485 delegate_->ItemAdded("dir2/subdir/name3", info1);
486 delegate_->ItemAdded("name4", info1);
488 delegate_->NoMoreItems();
490 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
491 ASSERT_EQ(3U, file_list_.size());
492 EXPECT_TRUE(file_list_[0].is_directory);
493 EXPECT_EQ("dir1", file_list_[0].name);
494 EXPECT_TRUE(file_list_[1].is_directory);
495 EXPECT_EQ("dir2", file_list_[1].name);
496 EXPECT_FALSE(file_list_[2].is_directory);
497 EXPECT_EQ("name4", file_list_[2].name);
499 EXPECT_EQ(base::File::FILE_OK,
500 ReadDir(base::FilePath(kDevicePath).Append("dir1")));
501 ASSERT_EQ(1U, file_list_.size());
502 EXPECT_FALSE(file_list_[0].is_directory);
503 EXPECT_EQ("name1", file_list_[0].name);
505 EXPECT_EQ(base::File::FILE_OK,
506 ReadDir(base::FilePath(kDevicePath).Append("dir2")));
507 ASSERT_EQ(2U, file_list_.size());
508 EXPECT_FALSE(file_list_[0].is_directory);
509 EXPECT_EQ("name2", file_list_[0].name);
510 EXPECT_TRUE(file_list_[1].is_directory);
511 EXPECT_EQ("subdir", file_list_[1].name);
513 EXPECT_EQ(base::File::FILE_OK,
514 ReadDir(base::FilePath(kDevicePath)
515 .Append("dir2").Append("subdir")));
516 ASSERT_EQ(1U, file_list_.size());
517 EXPECT_FALSE(file_list_[0].is_directory);
518 EXPECT_EQ("name3", file_list_[0].name);
520 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
521 ReadDir(base::FilePath(kDevicePath)
522 .Append("dir2").Append("subdir").Append("subdir")));
523 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
524 ReadDir(base::FilePath(kDevicePath)
525 .Append("dir3").Append("subdir")));
526 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
527 ReadDir(base::FilePath(kDevicePath).Append("dir3")));
530 TEST_F(MTPDeviceDelegateImplMacTest, TestDownload) {
531 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
532 base::Time t1 = base::Time::Now();
533 base::File::Info info;
535 info.is_directory = false;
536 info.is_symbolic_link = false;
537 info.last_modified = t1;
538 info.last_accessed = t1;
539 info.creation_time = t1;
540 std::string kTestFileName("filename");
541 base::scoped_nsobject<MockMTPICCameraFile> picture1(
542 [[MockMTPICCameraFile alloc]
543 init:base::SysUTF8ToNSString(kTestFileName)]);
544 [camera_ addMediaFile:picture1];
545 delegate_->ItemAdded(kTestFileName, info);
546 delegate_->NoMoreItems();
548 EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
549 ASSERT_EQ(1U, file_list_.size());
550 ASSERT_EQ("filename", file_list_[0].name);
552 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
553 DownloadFile(base::FilePath("/ic:id/nonexist"),
554 temp_dir_.path().Append("target")));
556 EXPECT_EQ(base::File::FILE_OK,
557 DownloadFile(base::FilePath("/ic:id/filename"),
558 temp_dir_.path().Append("target")));
559 std::string contents;
560 EXPECT_TRUE(base::ReadFileToString(temp_dir_.path().Append("target"),
562 EXPECT_EQ(kTestFileContents, contents);