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 "components/storage_monitor/image_capture_device.h" 6 7 #include "base/files/file_util.h" 8 #include "content/public/browser/browser_thread.h" 9 10 namespace storage_monitor { 11 12 namespace { 13 14 base::File::Error RenameFile(const base::FilePath& downloaded_filename, 15 const base::FilePath& desired_filename) { 16 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); 17 bool success = base::ReplaceFile(downloaded_filename, desired_filename, NULL); 18 return success ? base::File::FILE_OK : base::File::FILE_ERROR_NOT_FOUND; 19 } 20 21 void ReturnRenameResultToListener( 22 base::WeakPtr<ImageCaptureDeviceListener> listener, 23 const std::string& name, 24 const base::File::Error& result) { 25 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 26 if (listener) 27 listener->DownloadedFile(name, result); 28 } 29 30 base::Time NSDateToBaseTime(NSDate* date) { 31 return base::Time::FromDoubleT([date timeIntervalSince1970]); 32 } 33 34 base::FilePath PathForCameraItem(ICCameraItem* item) { 35 std::string name = base::SysNSStringToUTF8([item name]); 36 37 std::vector<std::string> components; 38 ICCameraFolder* folder = [item parentFolder]; 39 while (folder != nil) { 40 components.push_back(base::SysNSStringToUTF8([folder name])); 41 folder = [folder parentFolder]; 42 } 43 base::FilePath path; 44 for (std::vector<std::string>::reverse_iterator i = components.rbegin(); 45 i != components.rend(); ++i) { 46 path = path.Append(*i); 47 } 48 path = path.Append(name); 49 50 return path; 51 } 52 53 } // namespace 54 55 } // namespace storage_monitor 56 57 @implementation ImageCaptureDevice 58 59 - (id)initWithCameraDevice:(ICCameraDevice*)cameraDevice { 60 if ((self = [super init])) { 61 camera_.reset([cameraDevice retain]); 62 [camera_ setDelegate:self]; 63 closing_ = false; 64 } 65 return self; 66 } 67 68 - (void)dealloc { 69 // Make sure the session was closed and listener set to null 70 // before destruction. 71 DCHECK(![camera_ delegate]); 72 DCHECK(!listener_); 73 [super dealloc]; 74 } 75 76 - (void)setListener:(base::WeakPtr<storage_monitor::ImageCaptureDeviceListener>) 77 listener { 78 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 79 listener_ = listener; 80 } 81 82 - (void)open { 83 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 84 DCHECK(listener_); 85 [camera_ requestOpenSession]; 86 } 87 88 - (void)close { 89 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 90 closing_ = true; 91 [camera_ cancelDownload]; 92 [camera_ requestCloseSession]; 93 [camera_ setDelegate:nil]; 94 listener_.reset(); 95 } 96 97 - (void)eject { 98 [camera_ requestEjectOrDisconnect]; 99 } 100 101 - (void)downloadFile:(const std::string&)name 102 localPath:(const base::FilePath&)localPath { 103 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 104 105 // Find the file with that name and start download. 106 for (ICCameraItem* item in [camera_ mediaFiles]) { 107 std::string itemName = storage_monitor::PathForCameraItem(item).value(); 108 if (itemName == name) { 109 // To create save options for ImageCapture, we need to 110 // split the target filename into directory/name 111 // and encode the directory as a URL. 112 NSString* saveDirectory = 113 base::mac::FilePathToNSString(localPath.DirName()); 114 NSString* saveFilename = 115 base::mac::FilePathToNSString(localPath.BaseName()); 116 117 NSMutableDictionary* options = 118 [NSMutableDictionary dictionaryWithCapacity:3]; 119 [options setObject:[NSURL fileURLWithPath:saveDirectory isDirectory:YES] 120 forKey:ICDownloadsDirectoryURL]; 121 [options setObject:saveFilename forKey:ICSaveAsFilename]; 122 [options setObject:[NSNumber numberWithBool:YES] forKey:ICOverwrite]; 123 124 [camera_ requestDownloadFile:base::mac::ObjCCastStrict<ICCameraFile>(item) 125 options:options 126 downloadDelegate:self 127 didDownloadSelector: 128 @selector(didDownloadFile:error:options:contextInfo:) 129 contextInfo:NULL]; 130 return; 131 } 132 } 133 134 if (listener_) 135 listener_->DownloadedFile(name, base::File::FILE_ERROR_NOT_FOUND); 136 } 137 138 - (void)cameraDevice:(ICCameraDevice*)camera didAddItem:(ICCameraItem*)item { 139 base::File::Info info; 140 if ([[item UTI] isEqualToString:base::mac::CFToNSCast(kUTTypeFolder)]) 141 info.is_directory = true; 142 else 143 info.size = [base::mac::ObjCCastStrict<ICCameraFile>(item) fileSize]; 144 145 base::FilePath path = storage_monitor::PathForCameraItem(item); 146 147 info.last_modified = 148 storage_monitor::NSDateToBaseTime([item modificationDate]); 149 info.creation_time = storage_monitor::NSDateToBaseTime([item creationDate]); 150 info.last_accessed = info.last_modified; 151 152 if (listener_) 153 listener_->ItemAdded(path.value(), info); 154 } 155 156 - (void)cameraDevice:(ICCameraDevice*)camera didAddItems:(NSArray*)items { 157 for (ICCameraItem* item in items) 158 [self cameraDevice:camera didAddItem:item]; 159 } 160 161 - (void)didRemoveDevice:(ICDevice*)device { 162 device.delegate = NULL; 163 if (listener_) 164 listener_->DeviceRemoved(); 165 } 166 167 // Notifies that a session was opened with the given device; potentially 168 // with an error. 169 - (void)device:(ICDevice*)device didOpenSessionWithError:(NSError*)error { 170 if (error) 171 [self didRemoveDevice:camera_]; 172 } 173 174 - (void)device:(ICDevice*)device didEncounterError:(NSError*)error { 175 if (error && listener_) 176 listener_->DeviceRemoved(); 177 } 178 179 // When this message is received, all media metadata is now loaded. 180 - (void)deviceDidBecomeReadyWithCompleteContentCatalog:(ICDevice*)device { 181 if (listener_) 182 listener_->NoMoreItems(); 183 } 184 185 - (void)didDownloadFile:(ICCameraFile*)file 186 error:(NSError*)error 187 options:(NSDictionary*)options 188 contextInfo:(void*)contextInfo { 189 if (closing_) 190 return; 191 192 std::string name = storage_monitor::PathForCameraItem(file).value(); 193 194 if (error) { 195 DVLOG(1) << "error..." 196 << base::SysNSStringToUTF8([error localizedDescription]); 197 if (listener_) 198 listener_->DownloadedFile(name, base::File::FILE_ERROR_FAILED); 199 return; 200 } 201 202 std::string savedFilename = 203 base::SysNSStringToUTF8([options objectForKey:ICSavedFilename]); 204 std::string saveAsFilename = 205 base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]); 206 if (savedFilename == saveAsFilename) { 207 if (listener_) 208 listener_->DownloadedFile(name, base::File::FILE_OK); 209 return; 210 } 211 212 // ImageCapture did not save the file into the name we gave it in the 213 // options. It picks a new name according to its best lights, so we need 214 // to rename the file. 215 base::FilePath saveDir(base::SysNSStringToUTF8( 216 [[options objectForKey:ICDownloadsDirectoryURL] path])); 217 base::FilePath saveAsPath = saveDir.Append(saveAsFilename); 218 base::FilePath savedPath = saveDir.Append(savedFilename); 219 // Shared result value from file-copy closure to tell-listener closure. 220 content::BrowserThread::PostTaskAndReplyWithResult( 221 content::BrowserThread::FILE, 222 FROM_HERE, 223 base::Bind(&storage_monitor::RenameFile, savedPath, saveAsPath), 224 base::Bind( 225 &storage_monitor::ReturnRenameResultToListener, listener_, name)); 226 } 227 228 @end // ImageCaptureDevice 229