Home | History | Annotate | Download | only in storage_monitor
      1 // Copyright 2014 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_path.h"
     10 #include "base/files/scoped_temp_dir.h"
     11 #include "base/mac/foundation_util.h"
     12 #include "base/mac/sdk_forward_declarations.h"
     13 #include "base/memory/weak_ptr.h"
     14 #include "base/run_loop.h"
     15 #include "components/storage_monitor/image_capture_device.h"
     16 #include "components/storage_monitor/image_capture_device_manager.h"
     17 #include "components/storage_monitor/test_storage_monitor.h"
     18 #include "content/public/browser/browser_thread.h"
     19 #include "content/public/test/test_browser_thread_bundle.h"
     20 #include "testing/gtest/include/gtest/gtest.h"
     21 
     22 namespace {
     23 
     24 const char kDeviceId[] = "id";
     25 const char kTestFileContents[] = "test";
     26 
     27 }  // namespace
     28 
     29 // Private ICCameraDevice method needed to properly initialize the object.
     30 @interface NSObject (PrivateAPIICCameraDevice)
     31 - (id)initWithDictionary:(id)properties;
     32 @end
     33 
     34 @interface MockICCameraDevice : ICCameraDevice {
     35  @private
     36   base::scoped_nsobject<NSMutableArray> allMediaFiles_;
     37 }
     38 
     39 - (void)addMediaFile:(ICCameraFile*)file;
     40 
     41 @end
     42 
     43 @implementation MockICCameraDevice
     44 
     45 - (id)init {
     46   if ((self = [super initWithDictionary:[NSDictionary dictionary]])) {
     47   }
     48   return self;
     49 }
     50 
     51 - (NSString*)mountPoint {
     52   return @"mountPoint";
     53 }
     54 
     55 - (NSString*)name {
     56   return @"name";
     57 }
     58 
     59 - (NSString*)UUIDString {
     60   return base::SysUTF8ToNSString(kDeviceId);
     61 }
     62 
     63 - (ICDeviceType)type {
     64   return ICDeviceTypeCamera;
     65 }
     66 
     67 - (void)requestOpenSession {
     68 }
     69 
     70 - (void)requestCloseSession {
     71 }
     72 
     73 - (NSArray*)mediaFiles {
     74   return allMediaFiles_;
     75 }
     76 
     77 - (void)addMediaFile:(ICCameraFile*)file {
     78   if (!allMediaFiles_.get())
     79     allMediaFiles_.reset([[NSMutableArray alloc] init]);
     80   [allMediaFiles_ addObject:file];
     81 }
     82 
     83 // This method does approximately what the internal ImageCapture platform
     84 // library is observed to do: take the download save-as filename and mangle
     85 // it to attach an extension, then return that new filename to the caller
     86 // in the options.
     87 - (void)requestDownloadFile:(ICCameraFile*)file
     88                     options:(NSDictionary*)options
     89            downloadDelegate:(id<ICCameraDeviceDownloadDelegate>)downloadDelegate
     90         didDownloadSelector:(SEL)selector
     91                 contextInfo:(void*)contextInfo {
     92   base::FilePath saveDir(base::SysNSStringToUTF8(
     93       [[options objectForKey:ICDownloadsDirectoryURL] path]));
     94   std::string saveAsFilename =
     95       base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]);
     96   // It appears that the ImageCapture library adds an extension to the requested
     97   // filename. Do that here to require a rename.
     98   saveAsFilename += ".jpg";
     99   base::FilePath toBeSaved = saveDir.Append(saveAsFilename);
    100   ASSERT_EQ(static_cast<int>(strlen(kTestFileContents)),
    101             base::WriteFile(toBeSaved, kTestFileContents,
    102                             strlen(kTestFileContents)));
    103 
    104   NSMutableDictionary* returnOptions =
    105       [NSMutableDictionary dictionaryWithDictionary:options];
    106   [returnOptions setObject:base::SysUTF8ToNSString(saveAsFilename)
    107                     forKey:ICSavedFilename];
    108 
    109   [static_cast<NSObject<ICCameraDeviceDownloadDelegate>*>(downloadDelegate)
    110    didDownloadFile:file
    111              error:nil
    112            options:returnOptions
    113        contextInfo:contextInfo];
    114 }
    115 
    116 @end
    117 
    118 @interface MockICCameraFolder : ICCameraFolder {
    119  @private
    120   base::scoped_nsobject<NSString> name_;
    121 }
    122 
    123 - (id)initWithName:(NSString*)name;
    124 
    125 @end
    126 
    127 @implementation MockICCameraFolder
    128 
    129 - (id)initWithName:(NSString*)name {
    130   if ((self = [super init])) {
    131     name_.reset([name retain]);
    132   }
    133   return self;
    134 }
    135 
    136 - (NSString*)name {
    137   return name_;
    138 }
    139 
    140 - (ICCameraFolder*)parentFolder {
    141   return nil;
    142 }
    143 
    144 @end
    145 
    146 @interface MockICCameraFile : ICCameraFile {
    147  @private
    148   base::scoped_nsobject<NSString> name_;
    149   base::scoped_nsobject<NSDate> date_;
    150   base::scoped_nsobject<MockICCameraFolder> parent_;
    151 }
    152 
    153 - (id)init:(NSString*)name;
    154 - (void)setParent:(NSString*)parent;
    155 
    156 @end
    157 
    158 @implementation MockICCameraFile
    159 
    160 - (id)init:(NSString*)name {
    161   if ((self = [super init])) {
    162     name_.reset([name retain]);
    163     date_.reset([[NSDate dateWithNaturalLanguageString:@"12/12/12"] retain]);
    164   }
    165   return self;
    166 }
    167 
    168 - (void)setParent:(NSString*)parent {
    169   parent_.reset([[MockICCameraFolder alloc] initWithName:parent]);
    170 }
    171 
    172 - (ICCameraFolder*)parentFolder {
    173   return parent_.get();
    174 }
    175 
    176 - (NSString*)name {
    177   return name_;
    178 }
    179 
    180 - (NSString*)UTI {
    181   return base::mac::CFToNSCast(kUTTypeImage);
    182 }
    183 
    184 - (NSDate*)modificationDate {
    185   return date_.get();
    186 }
    187 
    188 - (NSDate*)creationDate {
    189   return date_.get();
    190 }
    191 
    192 - (off_t)fileSize {
    193   return 1000;
    194 }
    195 
    196 @end
    197 
    198 namespace storage_monitor {
    199 
    200 class TestCameraListener
    201     : public ImageCaptureDeviceListener,
    202       public base::SupportsWeakPtr<TestCameraListener> {
    203  public:
    204   TestCameraListener()
    205       : completed_(false),
    206         removed_(false),
    207         last_error_(base::File::FILE_ERROR_INVALID_URL) {}
    208   virtual ~TestCameraListener() {}
    209 
    210   virtual void ItemAdded(const std::string& name,
    211                          const base::File::Info& info) OVERRIDE {
    212     items_.push_back(name);
    213   }
    214 
    215   virtual void NoMoreItems() OVERRIDE {
    216     completed_ = true;
    217   }
    218 
    219   virtual void DownloadedFile(const std::string& name,
    220                               base::File::Error error) OVERRIDE {
    221     EXPECT_TRUE(content::BrowserThread::CurrentlyOn(
    222         content::BrowserThread::UI));
    223     downloads_.push_back(name);
    224     last_error_ = error;
    225   }
    226 
    227   virtual void DeviceRemoved() OVERRIDE {
    228     removed_ = true;
    229   }
    230 
    231   std::vector<std::string> items() const { return items_; }
    232   std::vector<std::string> downloads() const { return downloads_; }
    233   bool completed() const { return completed_; }
    234   bool removed() const { return removed_; }
    235   base::File::Error last_error() const { return last_error_; }
    236 
    237  private:
    238   std::vector<std::string> items_;
    239   std::vector<std::string> downloads_;
    240   bool completed_;
    241   bool removed_;
    242   base::File::Error last_error_;
    243 };
    244 
    245 class ImageCaptureDeviceManagerTest : public testing::Test {
    246  public:
    247   virtual void SetUp() OVERRIDE {
    248     monitor_ = TestStorageMonitor::CreateAndInstall();
    249   }
    250 
    251   virtual void TearDown() OVERRIDE {
    252     TestStorageMonitor::Destroy();
    253   }
    254 
    255   MockICCameraDevice* AttachDevice(ImageCaptureDeviceManager* manager) {
    256     // Ownership will be passed to the device browser delegate.
    257     base::scoped_nsobject<MockICCameraDevice> device(
    258         [[MockICCameraDevice alloc] init]);
    259     id<ICDeviceBrowserDelegate> delegate = manager->device_browser();
    260     [delegate deviceBrowser:nil didAddDevice:device moreComing:NO];
    261     return device.autorelease();
    262   }
    263 
    264   void DetachDevice(ImageCaptureDeviceManager* manager,
    265                     ICCameraDevice* device) {
    266     id<ICDeviceBrowserDelegate> delegate = manager->device_browser();
    267     [delegate deviceBrowser:nil didRemoveDevice:device moreGoing:NO];
    268   }
    269 
    270  protected:
    271   content::TestBrowserThreadBundle thread_bundle_;
    272   TestStorageMonitor* monitor_;
    273   TestCameraListener listener_;
    274 };
    275 
    276 TEST_F(ImageCaptureDeviceManagerTest, TestAttachDetach) {
    277   ImageCaptureDeviceManager manager;
    278   manager.SetNotifications(monitor_->receiver());
    279   ICCameraDevice* device = AttachDevice(&manager);
    280   std::vector<StorageInfo> devices = monitor_->GetAllAvailableStorages();
    281 
    282   ASSERT_EQ(1U, devices.size());
    283   EXPECT_EQ(std::string("ic:") + kDeviceId, devices[0].device_id());
    284 
    285   DetachDevice(&manager, device);
    286   devices = monitor_->GetAllAvailableStorages();
    287   ASSERT_EQ(0U, devices.size());
    288 };
    289 
    290 TEST_F(ImageCaptureDeviceManagerTest, OpenCamera) {
    291   ImageCaptureDeviceManager manager;
    292   manager.SetNotifications(monitor_->receiver());
    293   ICCameraDevice* device = AttachDevice(&manager);
    294 
    295   EXPECT_FALSE(ImageCaptureDeviceManager::deviceForUUID(
    296       "nonexistent"));
    297 
    298   base::scoped_nsobject<ImageCaptureDevice> camera(
    299       [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
    300 
    301   [camera setListener:listener_.AsWeakPtr()];
    302   [camera open];
    303 
    304   base::scoped_nsobject<MockICCameraFile> picture1(
    305       [[MockICCameraFile alloc] init:@"pic1"]);
    306   [camera cameraDevice:nil didAddItem:picture1];
    307   base::scoped_nsobject<MockICCameraFile> picture2(
    308       [[MockICCameraFile alloc] init:@"pic2"]);
    309   [camera cameraDevice:nil didAddItem:picture2];
    310   ASSERT_EQ(2U, listener_.items().size());
    311   EXPECT_EQ("pic1", listener_.items()[0]);
    312   EXPECT_EQ("pic2", listener_.items()[1]);
    313   EXPECT_FALSE(listener_.completed());
    314 
    315   [camera deviceDidBecomeReadyWithCompleteContentCatalog:nil];
    316 
    317   ASSERT_EQ(2U, listener_.items().size());
    318   EXPECT_TRUE(listener_.completed());
    319 
    320   [camera close];
    321   DetachDevice(&manager, device);
    322   EXPECT_FALSE(ImageCaptureDeviceManager::deviceForUUID(kDeviceId));
    323 }
    324 
    325 TEST_F(ImageCaptureDeviceManagerTest, RemoveCamera) {
    326   ImageCaptureDeviceManager manager;
    327   manager.SetNotifications(monitor_->receiver());
    328   ICCameraDevice* device = AttachDevice(&manager);
    329 
    330   base::scoped_nsobject<ImageCaptureDevice> camera(
    331       [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
    332 
    333   [camera setListener:listener_.AsWeakPtr()];
    334   [camera open];
    335 
    336   [camera didRemoveDevice:device];
    337   EXPECT_TRUE(listener_.removed());
    338 }
    339 
    340 TEST_F(ImageCaptureDeviceManagerTest, DownloadFile) {
    341   ImageCaptureDeviceManager manager;
    342   manager.SetNotifications(monitor_->receiver());
    343   MockICCameraDevice* device = AttachDevice(&manager);
    344 
    345   base::scoped_nsobject<ImageCaptureDevice> camera(
    346       [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
    347 
    348   [camera setListener:listener_.AsWeakPtr()];
    349   [camera open];
    350 
    351   std::string kTestFileName("pic1");
    352 
    353   base::scoped_nsobject<MockICCameraFile> picture1(
    354       [[MockICCameraFile alloc] init:base::SysUTF8ToNSString(kTestFileName)]);
    355   [device addMediaFile:picture1];
    356   [camera cameraDevice:nil didAddItem:picture1];
    357 
    358   base::ScopedTempDir temp_dir;
    359   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    360 
    361   EXPECT_EQ(0U, listener_.downloads().size());
    362 
    363   // Test that a nonexistent file we ask to be downloaded will
    364   // return us a not-found error.
    365   base::FilePath temp_file = temp_dir.path().Append("tempfile");
    366   [camera downloadFile:std::string("nonexistent") localPath:temp_file];
    367   base::RunLoop().RunUntilIdle();
    368   ASSERT_EQ(1U, listener_.downloads().size());
    369   EXPECT_EQ("nonexistent", listener_.downloads()[0]);
    370   EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, listener_.last_error());
    371 
    372   // Test that an existing file we ask to be downloaded will end up in
    373   // the location we specify. The mock system will copy testing file
    374   // contents to a separate filename, mimicking the ImageCaptureCore
    375   // library behavior. Our code then renames the file onto the requested
    376   // destination.
    377   [camera downloadFile:kTestFileName localPath:temp_file];
    378   base::RunLoop().RunUntilIdle();
    379 
    380   ASSERT_EQ(2U, listener_.downloads().size());
    381   EXPECT_EQ(kTestFileName, listener_.downloads()[1]);
    382   ASSERT_EQ(base::File::FILE_OK, listener_.last_error());
    383   char file_contents[5];
    384   ASSERT_EQ(4, base::ReadFile(temp_file, file_contents,
    385                               strlen(kTestFileContents)));
    386   EXPECT_EQ(kTestFileContents,
    387             std::string(file_contents, strlen(kTestFileContents)));
    388 
    389   [camera didRemoveDevice:device];
    390 }
    391 
    392 TEST_F(ImageCaptureDeviceManagerTest, TestSubdirectories) {
    393   ImageCaptureDeviceManager manager;
    394   manager.SetNotifications(monitor_->receiver());
    395   MockICCameraDevice* device = AttachDevice(&manager);
    396 
    397   base::scoped_nsobject<ImageCaptureDevice> camera(
    398       [ImageCaptureDeviceManager::deviceForUUID(kDeviceId) retain]);
    399 
    400   [camera setListener:listener_.AsWeakPtr()];
    401   [camera open];
    402 
    403   std::string kTestFileName("pic1");
    404   base::scoped_nsobject<MockICCameraFile> picture1(
    405       [[MockICCameraFile alloc] init:base::SysUTF8ToNSString(kTestFileName)]);
    406   [picture1 setParent:base::SysUTF8ToNSString("dir")];
    407   [device addMediaFile:picture1];
    408   [camera cameraDevice:nil didAddItem:picture1];
    409 
    410   base::ScopedTempDir temp_dir;
    411   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    412   base::FilePath temp_file = temp_dir.path().Append("tempfile");
    413 
    414   [camera downloadFile:("dir/" + kTestFileName) localPath:temp_file];
    415   base::RunLoop().RunUntilIdle();
    416 
    417   char file_contents[5];
    418   ASSERT_EQ(4, base::ReadFile(temp_file, file_contents,
    419                               strlen(kTestFileContents)));
    420   EXPECT_EQ(kTestFileContents,
    421             std::string(file_contents, strlen(kTestFileContents)));
    422 
    423   [camera didRemoveDevice:device];
    424 }
    425 
    426 }  // namespace storage_monitor
    427