blob: d0fea0272a7c699689aadfccc2cb4e6a3669e188 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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/bind.h"
#include "base/files/file_util.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.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, NULL);
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::Time NSDateToBaseTime(NSDate* date) {
return base::Time::FromDoubleT([date timeIntervalSince1970]);
}
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 (std::vector<std::string>::reverse_iterator i = components.rbegin();
i != components.rend(); ++i) {
path = path.Append(*i);
}
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_ requestEjectOrDisconnect];
}
- (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:NULL];
return;
}
}
if (listener_)
listener_->DownloadedFile(name, base::File::FILE_ERROR_NOT_FOUND);
}
- (void)cameraDevice:(ICCameraDevice*)camera didAddItem:(ICCameraItem*)item {
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 =
storage_monitor::NSDateToBaseTime([item modificationDate]);
info.creation_time = storage_monitor::NSDateToBaseTime([item creationDate]);
info.last_accessed = info.last_modified;
if (listener_)
listener_->ItemAdded(path.value(), info);
}
- (void)cameraDevice:(ICCameraDevice*)camera didAddItems:(NSArray*)items {
for (ICCameraItem* item in items)
[self cameraDevice:camera didAddItem:item];
}
- (void)didRemoveDevice:(ICDevice*)device {
device.delegate = NULL;
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();
}
// When this message is received, all media metadata is now loaded.
- (void)deviceDidBecomeReadyWithCompleteContentCatalog:(ICDevice*)device {
if (listener_)
listener_->NoMoreItems();
}
- (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::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::Bind(&storage_monitor::RenameFile, savedPath, saveAsPath),
base::Bind(&storage_monitor::ReturnRenameResultToListener, listener_,
name));
}
// MacOS 10.14 SDK methods, not yet implemented (https://crbug.com/849689)
- (void)cameraDevice:(ICCameraDevice*)camera
didRenameItems:(NSArray<ICCameraItem*>*)items {
NOTIMPLEMENTED();
}
- (void)cameraDevice:(ICCameraDevice*)camera didRemoveItem:(ICCameraItem*)item {
NOTIMPLEMENTED();
}
- (void)cameraDevice:(ICCameraDevice*)camera
didCompleteDeleteFilesWithError:(NSError*)error {
NOTIMPLEMENTED();
}
- (void)cameraDeviceDidChangeCapability:(ICCameraDevice*)camera {
NOTIMPLEMENTED();
}
- (void)cameraDevice:(ICCameraDevice*)camera
didReceiveThumbnailForItem:(ICCameraItem*)item {
NOTIMPLEMENTED();
}
- (void)cameraDevice:(ICCameraDevice*)camera
didReceiveMetadataForItem:(ICCameraItem*)item {
NOTIMPLEMENTED();
}
- (void)cameraDevice:(ICCameraDevice*)camera
didReceivePTPEvent:(NSData*)eventData {
NOTIMPLEMENTED();
}
@end // ImageCaptureDevice