| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "components/storage_monitor/image_capture_device.h" |
| |
| #include "base/containers/adapters.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| namespace storage_monitor { |
| |
| namespace { |
| |
| base::File::Error RenameFile(const base::FilePath& downloaded_filename, |
| const base::FilePath& desired_filename) { |
| bool success = |
| base::ReplaceFile(downloaded_filename, desired_filename, nullptr); |
| return success ? base::File::FILE_OK : base::File::FILE_ERROR_NOT_FOUND; |
| } |
| |
| void ReturnRenameResultToListener( |
| base::WeakPtr<ImageCaptureDeviceListener> listener, |
| const std::string& name, |
| const base::File::Error& result) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (listener) { |
| listener->DownloadedFile(name, result); |
| } |
| } |
| |
| base::FilePath PathForCameraItem(ICCameraItem* item) { |
| std::string name = base::SysNSStringToUTF8([item name]); |
| |
| std::vector<std::string> components; |
| ICCameraFolder* folder = [item parentFolder]; |
| while (folder != nil) { |
| components.push_back(base::SysNSStringToUTF8([folder name])); |
| folder = [folder parentFolder]; |
| } |
| base::FilePath path; |
| for (const std::string& component : base::Reversed(components)) { |
| path = path.Append(component); |
| } |
| path = path.Append(name); |
| |
| return path; |
| } |
| |
| } // namespace |
| |
| } // namespace storage_monitor |
| |
| @implementation ImageCaptureDevice |
| |
| - (instancetype)initWithCameraDevice:(ICCameraDevice*)cameraDevice { |
| if ((self = [super init])) { |
| _camera.reset([cameraDevice retain]); |
| [_camera setDelegate:self]; |
| _closing = false; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| // Make sure the session was closed and listener set to null |
| // before destruction. |
| DCHECK(![_camera delegate]); |
| DCHECK(!_listener); |
| [super dealloc]; |
| } |
| |
| - (void)setListener:(base::WeakPtr<storage_monitor::ImageCaptureDeviceListener>) |
| listener { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| _listener = listener; |
| } |
| |
| - (void)open { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(_listener); |
| [_camera requestOpenSession]; |
| } |
| |
| - (void)close { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| _closing = true; |
| [_camera cancelDownload]; |
| [_camera requestCloseSession]; |
| [_camera setDelegate:nil]; |
| _listener.reset(); |
| } |
| |
| - (void)eject { |
| [_camera requestEject]; |
| } |
| |
| - (void)downloadFile:(const std::string&)name |
| localPath:(const base::FilePath&)localPath { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Find the file with that name and start download. |
| for (ICCameraItem* item in [_camera mediaFiles]) { |
| std::string itemName = storage_monitor::PathForCameraItem(item).value(); |
| if (itemName == name) { |
| // To create save options for ImageCapture, we need to |
| // split the target filename into directory/name |
| // and encode the directory as a URL. |
| NSString* saveDirectory = |
| base::mac::FilePathToNSString(localPath.DirName()); |
| NSString* saveFilename = |
| base::mac::FilePathToNSString(localPath.BaseName()); |
| |
| NSMutableDictionary* options = |
| [NSMutableDictionary dictionaryWithCapacity:3]; |
| options[ICDownloadsDirectoryURL] = |
| [NSURL fileURLWithPath:saveDirectory isDirectory:YES]; |
| options[ICSaveAsFilename] = saveFilename; |
| options[ICOverwrite] = @YES; |
| |
| [_camera requestDownloadFile:base::mac::ObjCCastStrict<ICCameraFile>(item) |
| options:options |
| downloadDelegate:self |
| didDownloadSelector:@selector |
| (didDownloadFile:error:options:contextInfo:) |
| contextInfo:nullptr]; |
| return; |
| } |
| } |
| |
| if (_listener) { |
| _listener->DownloadedFile(name, base::File::FILE_ERROR_NOT_FOUND); |
| } |
| } |
| |
| // ----- ICDeviceDelegate (super-protocol of ICCameraDeviceDelegate) ----- |
| |
| - (void)didRemoveDevice:(ICDevice*)device { |
| device.delegate = nullptr; |
| if (_listener) { |
| _listener->DeviceRemoved(); |
| } |
| } |
| |
| // Notifies that a session was opened with the given device; potentially |
| // with an error. |
| - (void)device:(ICDevice*)device didOpenSessionWithError:(NSError*)error { |
| if (error) { |
| [self didRemoveDevice:_camera]; |
| } |
| } |
| |
| - (void)device:(ICDevice*)device didEncounterError:(NSError*)error { |
| if (error && _listener) { |
| _listener->DeviceRemoved(); |
| } |
| } |
| |
| // Various ICDeviceDelegate calls that are not used but need to exist as part of |
| // a full delegate implementation. |
| |
| - (void)device:(ICDevice*)device didCloseSessionWithError:(NSError*)error { |
| } |
| |
| // ----- ICCameraDeviceDelegate ----- |
| |
| - (void)cameraDevice:(ICCameraDevice*)camera |
| didAddItems:(NSArray<ICCameraItem*>*)items { |
| for (ICCameraItem* item in items) { |
| base::File::Info info; |
| if ([[item UTI] isEqualToString:base::mac::CFToNSCast(kUTTypeFolder)]) { |
| info.is_directory = true; |
| } else { |
| info.size = [base::mac::ObjCCastStrict<ICCameraFile>(item) fileSize]; |
| } |
| |
| base::FilePath path = storage_monitor::PathForCameraItem(item); |
| |
| info.last_modified = base::Time::FromNSDate([item modificationDate]); |
| info.creation_time = base::Time::FromNSDate([item creationDate]); |
| info.last_accessed = info.last_modified; |
| |
| if (_listener) { |
| _listener->ItemAdded(path.value(), info); |
| } |
| } |
| } |
| |
| // When this message is received, all media metadata is now loaded. |
| - (void)deviceDidBecomeReadyWithCompleteContentCatalog:(ICDevice*)device { |
| if (_listener) { |
| _listener->NoMoreItems(); |
| } |
| } |
| |
| // Various ICCameraDeviceDelegate calls that are not used but need to exist as |
| // part of a full delegate implementation. |
| |
| - (void)cameraDevice:(ICCameraDevice*)camera didRemoveItems:(NSArray*)items { |
| } |
| |
| - (void)cameraDevice:(ICCameraDevice*)camera |
| didReceiveThumbnail:(CGImageRef)thumbnail |
| forItem:(ICCameraItem*)item |
| error:(NSError*)error { |
| } |
| |
| - (void)cameraDevice:(ICCameraDevice*)camera |
| didReceiveMetadata:(NSDictionary*)metadata |
| forItem:(ICCameraItem*)item |
| error:(NSError*)error { |
| } |
| |
| - (void)cameraDevice:(ICCameraDevice*)camera |
| didRenameItems:(NSArray<ICCameraItem*>*)items { |
| } |
| |
| - (void)cameraDeviceDidChangeCapability:(ICCameraDevice*)camera { |
| } |
| |
| - (void)cameraDevice:(ICCameraDevice*)camera |
| didReceivePTPEvent:(NSData*)eventData { |
| } |
| |
| - (void)cameraDeviceDidRemoveAccessRestriction:(ICDevice*)device { |
| } |
| |
| - (void)cameraDeviceDidEnableAccessRestriction:(ICDevice*)device { |
| } |
| |
| // ----- ICCameraDeviceDownloadDelegate ----- |
| |
| - (void)didDownloadFile:(ICCameraFile*)file |
| error:(NSError*)error |
| options:(NSDictionary*)options |
| contextInfo:(void*)contextInfo { |
| if (_closing) { |
| return; |
| } |
| |
| std::string name = storage_monitor::PathForCameraItem(file).value(); |
| |
| if (error) { |
| DVLOG(1) << "error..." |
| << base::SysNSStringToUTF8([error localizedDescription]); |
| if (_listener) { |
| _listener->DownloadedFile(name, base::File::FILE_ERROR_FAILED); |
| } |
| return; |
| } |
| |
| std::string savedFilename = base::SysNSStringToUTF8(options[ICSavedFilename]); |
| std::string saveAsFilename = |
| base::SysNSStringToUTF8(options[ICSaveAsFilename]); |
| if (savedFilename == saveAsFilename) { |
| if (_listener) { |
| _listener->DownloadedFile(name, base::File::FILE_OK); |
| } |
| return; |
| } |
| |
| // ImageCapture did not save the file into the name we gave it in the |
| // options. It picks a new name according to its best lights, so we need |
| // to rename the file. |
| base::FilePath saveDir( |
| base::SysNSStringToUTF8([options[ICDownloadsDirectoryURL] path])); |
| base::FilePath saveAsPath = saveDir.Append(saveAsFilename); |
| base::FilePath savedPath = saveDir.Append(savedFilename); |
| // Shared result value from file-copy closure to tell-listener closure. |
| // This is worth blocking shutdown, as otherwise a file that has been |
| // downloaded will be incorrectly named. |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}, |
| base::BindOnce(&storage_monitor::RenameFile, savedPath, saveAsPath), |
| base::BindOnce(&storage_monitor::ReturnRenameResultToListener, _listener, |
| name)); |
| } |
| |
| @end // ImageCaptureDevice |