1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "components/storage_monitor/image_capture_device.h"
7 #include <ImageCaptureCore/ImageCaptureCore.h>
8 #import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
10 #include "base/apple/bridging.h"
11 #include "base/apple/foundation_util.h"
12 #include "base/containers/adapters.h"
13 #include "base/files/file_util.h"
14 #include "base/functional/bind.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/task/task_traits.h"
17 #include "base/task/thread_pool.h"
18 #include "content/public/browser/browser_thread.h"
20 namespace storage_monitor {
24 base::File::Error RenameFile(const base::FilePath& downloaded_filename,
25 const base::FilePath& desired_filename) {
27 base::ReplaceFile(downloaded_filename, desired_filename, nullptr);
28 return success ? base::File::FILE_OK : base::File::FILE_ERROR_NOT_FOUND;
31 void ReturnRenameResultToListener(
32 base::WeakPtr<ImageCaptureDeviceListener> listener,
33 const std::string& name,
34 const base::File::Error& result) {
35 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
37 listener->DownloadedFile(name, result);
41 base::FilePath PathForCameraItem(ICCameraItem* item) {
42 std::string name = base::SysNSStringToUTF8(item.name);
44 std::vector<std::string> components;
45 ICCameraFolder* folder = item.parentFolder;
46 while (folder != nil) {
47 components.push_back(base::SysNSStringToUTF8(folder.name));
48 folder = folder.parentFolder;
51 for (const std::string& component : base::Reversed(components)) {
52 path = path.Append(component);
54 path = path.Append(name);
61 } // namespace storage_monitor
63 @implementation ImageCaptureDevice {
64 ICCameraDevice* __strong _camera;
65 base::WeakPtr<storage_monitor::ImageCaptureDeviceListener> _listener;
69 - (instancetype)initWithCameraDevice:(ICCameraDevice*)cameraDevice {
70 if ((self = [super init])) {
71 _camera = cameraDevice;
72 _camera.delegate = self;
78 // Make sure the session was closed and listener set to null
79 // before destruction.
80 DCHECK(!_camera.delegate);
84 - (void)setListener:(base::WeakPtr<storage_monitor::ImageCaptureDeviceListener>)
86 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
91 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
93 [_camera requestOpenSession];
97 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
99 [_camera cancelDownload];
100 [_camera requestCloseSession];
101 _camera.delegate = nil;
106 [_camera requestEject];
109 - (void)downloadFile:(const std::string&)name
110 localPath:(const base::FilePath&)localPath {
111 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
113 // Find the file with that name and start download.
114 for (ICCameraItem* item in _camera.mediaFiles) {
115 std::string itemName = storage_monitor::PathForCameraItem(item).value();
116 if (itemName == name) {
117 // To create save options for ImageCapture, we need to split the target
118 // filename into directory/name and encode the directory as a URL.
119 NSURL* saveDirectory = base::apple::FilePathToNSURL(localPath.DirName());
120 NSString* saveFilename =
121 base::apple::FilePathToNSString(localPath.BaseName());
123 NSDictionary* options = @{
124 ICDownloadsDirectoryURL : saveDirectory,
125 ICSaveAsFilename : saveFilename,
130 requestDownloadFile:base::apple::ObjCCastStrict<ICCameraFile>(item)
132 downloadDelegate:self
133 didDownloadSelector:@selector(didDownloadFile:
134 error:options:contextInfo:)
135 contextInfo:nullptr];
141 _listener->DownloadedFile(name, base::File::FILE_ERROR_NOT_FOUND);
145 // ----- ICDeviceDelegate (super-protocol of ICCameraDeviceDelegate) -----
147 - (void)didRemoveDevice:(ICDevice*)device {
148 device.delegate = nullptr;
150 _listener->DeviceRemoved();
154 // Notifies that a session was opened with the given device; potentially
156 - (void)device:(ICDevice*)device didOpenSessionWithError:(NSError*)error {
158 [self didRemoveDevice:_camera];
162 - (void)device:(ICDevice*)device didEncounterError:(NSError*)error {
163 if (error && _listener) {
164 _listener->DeviceRemoved();
168 // Various ICDeviceDelegate calls that are not used but need to exist as part of
169 // a full delegate implementation.
171 - (void)device:(ICDevice*)device didCloseSessionWithError:(NSError*)error {
174 // ----- ICCameraDeviceDelegate -----
176 - (void)cameraDevice:(ICCameraDevice*)camera
177 didAddItems:(NSArray<ICCameraItem*>*)items {
178 NSString* folderIdentifier;
179 if (@available(macOS 11, *)) {
180 folderIdentifier = UTTypeFolder.identifier;
182 folderIdentifier = base::apple::CFToNSPtrCast(kUTTypeFolder);
185 for (ICCameraItem* item in items) {
186 base::File::Info info;
187 if ([item.UTI isEqualToString:folderIdentifier]) {
188 info.is_directory = true;
190 info.size = base::apple::ObjCCastStrict<ICCameraFile>(item).fileSize;
193 base::FilePath path = storage_monitor::PathForCameraItem(item);
195 info.last_modified = base::Time::FromNSDate(item.modificationDate);
196 info.creation_time = base::Time::FromNSDate(item.creationDate);
197 info.last_accessed = info.last_modified;
200 _listener->ItemAdded(path.value(), info);
205 // When this message is received, all media metadata is now loaded.
206 - (void)deviceDidBecomeReadyWithCompleteContentCatalog:(ICDevice*)device {
208 _listener->NoMoreItems();
212 // Various ICCameraDeviceDelegate calls that are not used but need to exist as
213 // part of a full delegate implementation.
215 - (void)cameraDevice:(ICCameraDevice*)camera didRemoveItems:(NSArray*)items {
218 - (void)cameraDevice:(ICCameraDevice*)camera
219 didReceiveThumbnail:(CGImageRef)thumbnail
220 forItem:(ICCameraItem*)item
221 error:(NSError*)error {
224 - (void)cameraDevice:(ICCameraDevice*)camera
225 didReceiveMetadata:(NSDictionary*)metadata
226 forItem:(ICCameraItem*)item
227 error:(NSError*)error {
230 - (void)cameraDevice:(ICCameraDevice*)camera
231 didRenameItems:(NSArray<ICCameraItem*>*)items {
234 - (void)cameraDeviceDidChangeCapability:(ICCameraDevice*)camera {
237 - (void)cameraDevice:(ICCameraDevice*)camera
238 didReceivePTPEvent:(NSData*)eventData {
241 - (void)cameraDeviceDidRemoveAccessRestriction:(ICDevice*)device {
244 - (void)cameraDeviceDidEnableAccessRestriction:(ICDevice*)device {
247 // ----- ICCameraDeviceDownloadDelegate -----
249 - (void)didDownloadFile:(ICCameraFile*)file
250 error:(NSError*)error
251 options:(NSDictionary*)options
252 contextInfo:(void*)contextInfo {
257 std::string name = storage_monitor::PathForCameraItem(file).value();
260 DVLOG(1) << "error..."
261 << base::SysNSStringToUTF8(error.localizedDescription);
263 _listener->DownloadedFile(name, base::File::FILE_ERROR_FAILED);
268 std::string savedFilename = base::SysNSStringToUTF8(options[ICSavedFilename]);
269 std::string saveAsFilename =
270 base::SysNSStringToUTF8(options[ICSaveAsFilename]);
271 if (savedFilename == saveAsFilename) {
273 _listener->DownloadedFile(name, base::File::FILE_OK);
278 // ImageCapture did not save the file into the name we gave it in the
279 // options. It picks a new name according to its best lights, so we need
280 // to rename the file.
281 base::FilePath saveDir(
282 base::SysNSStringToUTF8([options[ICDownloadsDirectoryURL] path]));
283 base::FilePath saveAsPath = saveDir.Append(saveAsFilename);
284 base::FilePath savedPath = saveDir.Append(savedFilename);
285 // Shared result value from file-copy closure to tell-listener closure.
286 // This is worth blocking shutdown, as otherwise a file that has been
287 // downloaded will be incorrectly named.
288 base::ThreadPool::PostTaskAndReplyWithResult(
290 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
291 base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
292 base::BindOnce(&storage_monitor::RenameFile, savedPath, saveAsPath),
293 base::BindOnce(&storage_monitor::ReturnRenameResultToListener, _listener,
297 @end // ImageCaptureDevice