Home | History | Annotate | Download | only in mac
      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 #import <Foundation/Foundation.h>
      6 #import <ImageCaptureCore/ImageCaptureCore.h>
      7 
      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/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"
     27 
     28 namespace {
     29 
     30 const char kDeviceId[] = "id";
     31 const char kDevicePath[] = "/ic:id";
     32 const char kTestFileContents[] = "test";
     33 
     34 }  // namespace
     35 
     36 @interface MockMTPICCameraDevice : ICCameraDevice {
     37  @private
     38   base::scoped_nsobject<NSMutableArray> allMediaFiles_;
     39 }
     40 
     41 - (void)addMediaFile:(ICCameraFile*)file;
     42 
     43 @end
     44 
     45 @implementation MockMTPICCameraDevice
     46 
     47 - (NSString*)mountPoint {
     48   return @"mountPoint";
     49 }
     50 
     51 - (NSString*)name {
     52   return @"name";
     53 }
     54 
     55 - (NSString*)UUIDString {
     56   return base::SysUTF8ToNSString(kDeviceId);
     57 }
     58 
     59 - (ICDeviceType)type {
     60   return ICDeviceTypeCamera;
     61 }
     62 
     63 - (void)requestOpenSession {
     64 }
     65 
     66 - (void)requestCloseSession {
     67 }
     68 
     69 - (NSArray*)mediaFiles {
     70   return allMediaFiles_;
     71 }
     72 
     73 - (void)addMediaFile:(ICCameraFile*)file {
     74   if (!allMediaFiles_.get())
     75     allMediaFiles_.reset([[NSMutableArray alloc] init]);
     76   [allMediaFiles_ addObject:file];
     77 }
     78 
     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)));
     95 
     96   NSMutableDictionary* returnOptions =
     97       [NSMutableDictionary dictionaryWithDictionary:options];
     98   [returnOptions setObject:base::SysUTF8ToNSString(saveAsFilename)
     99                     forKey:ICSavedFilename];
    100 
    101   [static_cast<NSObject<ICCameraDeviceDownloadDelegate>*>(downloadDelegate)
    102    didDownloadFile:file
    103              error:nil
    104            options:returnOptions
    105        contextInfo:contextInfo];
    106 }
    107 
    108 @end
    109 
    110 @interface MockMTPICCameraFile : ICCameraFile {
    111  @private
    112   base::scoped_nsobject<NSString> name_;
    113   base::scoped_nsobject<NSDate> date_;
    114 }
    115 
    116 - (id)init:(NSString*)name;
    117 
    118 @end
    119 
    120 @implementation MockMTPICCameraFile
    121 
    122 - (id)init:(NSString*)name {
    123   if ((self = [super init])) {
    124     name_.reset([name retain]);
    125     date_.reset([[NSDate dateWithNaturalLanguageString:@"12/12/12"] retain]);
    126   }
    127   return self;
    128 }
    129 
    130 - (NSString*)name {
    131   return name_.get();
    132 }
    133 
    134 - (NSString*)UTI {
    135   return base::mac::CFToNSCast(kUTTypeImage);
    136 }
    137 
    138 - (NSDate*)modificationDate {
    139   return date_.get();
    140 }
    141 
    142 - (NSDate*)creationDate {
    143   return date_.get();
    144 }
    145 
    146 - (off_t)fileSize {
    147   return 1000;
    148 }
    149 
    150 @end
    151 
    152 class MTPDeviceDelegateImplMacTest : public testing::Test {
    153  public:
    154   MTPDeviceDelegateImplMacTest() : camera_(NULL), delegate_(NULL) {}
    155 
    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());
    164 
    165     storage_monitor::TestStorageMonitor* monitor =
    166         storage_monitor::TestStorageMonitor::CreateAndInstall();
    167     manager_.SetNotifications(monitor->receiver());
    168 
    169     camera_ = [MockMTPICCameraDevice alloc];
    170     id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
    171     [delegate deviceBrowser:nil didAddDevice:camera_ moreComing:NO];
    172 
    173     delegate_ = new MTPDeviceDelegateImplMac(kDeviceId, kDevicePath);
    174   }
    175 
    176   virtual void TearDown() OVERRIDE {
    177     id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
    178     [delegate deviceBrowser:nil didRemoveDevice:camera_ moreGoing:NO];
    179 
    180     delegate_->CancelPendingTasksAndDeleteDelegate();
    181 
    182     storage_monitor::TestStorageMonitor::Destroy();
    183 
    184     io_thread_->Stop();
    185   }
    186 
    187   void OnError(base::WaitableEvent* event, base::File::Error error) {
    188     error_ = error;
    189     event->Signal();
    190   }
    191 
    192   void OverlappedOnError(base::WaitableEvent* event,
    193                          base::File::Error error) {
    194     overlapped_error_ = error;
    195     event->Signal();
    196   }
    197 
    198   void OnFileInfo(base::WaitableEvent* event,
    199                   const base::File::Info& info) {
    200     error_ = base::File::FILE_OK;
    201     info_ = info;
    202     event->Signal();
    203   }
    204 
    205   void OnReadDir(base::WaitableEvent* event,
    206                  const fileapi::AsyncFileUtil::EntryList& files,
    207                  bool has_more) {
    208     error_ = base::File::FILE_OK;
    209     ASSERT_FALSE(has_more);
    210     file_list_ = files;
    211     event->Signal();
    212   }
    213 
    214   void OverlappedOnReadDir(base::WaitableEvent* event,
    215                            const fileapi::AsyncFileUtil::EntryList& files,
    216                            bool has_more) {
    217     overlapped_error_ = base::File::FILE_OK;
    218     ASSERT_FALSE(has_more);
    219     overlapped_file_list_ = files;
    220     event->Signal();
    221   }
    222 
    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;
    227     event->Signal();
    228   }
    229 
    230   base::File::Error GetFileInfo(const base::FilePath& path,
    231                                 base::File::Info* info) {
    232     base::WaitableEvent wait(true, false);
    233     delegate_->GetFileInfo(
    234       path,
    235       base::Bind(&MTPDeviceDelegateImplMacTest::OnFileInfo,
    236                  base::Unretained(this),
    237                  &wait),
    238       base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
    239                  base::Unretained(this),
    240                  &wait));
    241     base::RunLoop loop;
    242     loop.RunUntilIdle();
    243     EXPECT_TRUE(wait.IsSignaled());
    244     *info = info_;
    245     return error_;
    246   }
    247 
    248   base::File::Error ReadDir(const base::FilePath& path) {
    249     base::WaitableEvent wait(true, false);
    250     delegate_->ReadDirectory(
    251         path,
    252         base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
    253                    base::Unretained(this),
    254                    &wait),
    255         base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
    256                    base::Unretained(this),
    257                    &wait));
    258     base::RunLoop loop;
    259     loop.RunUntilIdle();
    260     wait.Wait();
    261     return error_;
    262   }
    263 
    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(
    269         path, local_path,
    270         base::Bind(&MTPDeviceDelegateImplMacTest::OnDownload,
    271                    base::Unretained(this),
    272                    &wait),
    273         base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
    274                    base::Unretained(this),
    275                    &wait));
    276     base::RunLoop loop;
    277     loop.RunUntilIdle();
    278     wait.Wait();
    279     return error_;
    280   }
    281 
    282  protected:
    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_;
    291 
    292   // This object needs special deletion inside the above |task_runner_|.
    293   MTPDeviceDelegateImplMac* delegate_;
    294 
    295   base::File::Error error_;
    296   base::File::Info info_;
    297   fileapi::AsyncFileUtil::EntryList file_list_;
    298 
    299   base::File::Error overlapped_error_;
    300   fileapi::AsyncFileUtil::EntryList overlapped_file_list_;
    301 
    302  private:
    303   DISALLOW_COPY_AND_ASSIGN(MTPDeviceDelegateImplMacTest);
    304 };
    305 
    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));
    315 
    316   // Signal the delegate that no files are coming.
    317   delegate_->NoMoreItems();
    318 
    319   EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
    320   EXPECT_EQ(0U, file_list_.size());
    321 }
    322 
    323 TEST_F(MTPDeviceDelegateImplMacTest, TestOverlappedReadDir) {
    324   base::Time time1 = base::Time::Now();
    325   base::File::Info info1;
    326   info1.size = 1;
    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);
    333 
    334   base::WaitableEvent wait(true, false);
    335 
    336   delegate_->ReadDirectory(
    337       base::FilePath(kDevicePath),
    338       base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
    339                  base::Unretained(this),
    340                  &wait),
    341       base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
    342                  base::Unretained(this),
    343                  &wait));
    344 
    345   delegate_->ReadDirectory(
    346       base::FilePath(kDevicePath),
    347       base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnReadDir,
    348                  base::Unretained(this),
    349                  &wait),
    350       base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnError,
    351                  base::Unretained(this),
    352                  &wait));
    353 
    354 
    355   // Signal the delegate that no files are coming.
    356   delegate_->NoMoreItems();
    357 
    358   base::RunLoop loop;
    359   loop.RunUntilIdle();
    360   wait.Wait();
    361 
    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());
    366 }
    367 
    368 TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) {
    369   base::Time time1 = base::Time::Now();
    370   base::File::Info info1;
    371   info1.size = 1;
    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);
    378 
    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);
    387 
    388   info1.size = 2;
    389   delegate_->ItemAdded("name2", info1);
    390   delegate_->NoMoreItems();
    391 
    392   EXPECT_EQ(base::File::FILE_OK,
    393             GetFileInfo(base::FilePath("/ic:id/name2"), &info));
    394   EXPECT_EQ(info1.size, info.size);
    395 
    396   EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
    397 
    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);
    402 
    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);
    406 }
    407 
    408 TEST_F(MTPDeviceDelegateImplMacTest, TestDirectoriesAndSorting) {
    409   base::Time time1 = base::Time::Now();
    410   base::File::Info info1;
    411   info1.size = 1;
    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);
    418 
    419   info1.is_directory = true;
    420   delegate_->ItemAdded("dir2", info1);
    421   delegate_->ItemAdded("dir1", info1);
    422 
    423   info1.is_directory = false;
    424   delegate_->ItemAdded("name1", info1);
    425   delegate_->NoMoreItems();
    426 
    427   EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
    428 
    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);
    435 
    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);
    439 }
    440 
    441 TEST_F(MTPDeviceDelegateImplMacTest, SubDirectories) {
    442   base::Time time1 = base::Time::Now();
    443   base::File::Info info1;
    444   info1.size = 0;
    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);
    451 
    452   info1.size = 1;
    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);
    459 
    460   info1.is_directory = true;
    461   info1.size = 0;
    462   delegate_->ItemAdded("dir2", info1);
    463 
    464   info1.is_directory = false;
    465   info1.size = 1;
    466   delegate_->ItemAdded("dir2/name2", info1);
    467 
    468   info1.is_directory = true;
    469   info1.size = 0;
    470   delegate_->ItemAdded("dir2/subdir", info1);
    471 
    472   info1.is_directory = false;
    473   info1.size = 1;
    474   delegate_->ItemAdded("dir2/subdir/name3", info1);
    475   delegate_->ItemAdded("name4", info1);
    476 
    477   delegate_->NoMoreItems();
    478 
    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);
    487 
    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);
    493 
    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);
    501 
    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);
    508 
    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")));
    517 }
    518 
    519 TEST_F(MTPDeviceDelegateImplMacTest, TestDownload) {
    520   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    521   base::Time t1 = base::Time::Now();
    522   base::File::Info info;
    523   info.size = 4;
    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();
    536 
    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);
    540 
    541   EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
    542             DownloadFile(base::FilePath("/ic:id/nonexist"),
    543                          temp_dir_.path().Append("target")));
    544 
    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"),
    550                                      &contents));
    551   EXPECT_EQ(kTestFileContents, contents);
    552 }
    553