1 // Copyright (c) 2012 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.
5 #include "chrome/browser/storage_monitor/storage_monitor_mac.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/mac_util.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/storage_monitor/image_capture_device_manager.h"
12 #include "chrome/browser/storage_monitor/media_storage_util.h"
13 #include "chrome/browser/storage_monitor/storage_info.h"
14 #include "content/public/browser/browser_thread.h"
18 const char kDiskImageModelName[] = "Disk Image";
20 string16 GetUTF16FromDictionary(CFDictionaryRef dictionary, CFStringRef key) {
22 base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key);
25 return base::SysCFStringRefToUTF16(value);
28 string16 JoinName(const string16& name, const string16& addition) {
33 return name + static_cast<char16>(' ') + addition;
36 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
38 return StorageInfo::FIXED_MASS_STORAGE;
40 return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
41 return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
44 StorageInfo BuildStorageInfo(
45 CFDictionaryRef dict, std::string* bsd_name) {
46 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
48 CFStringRef device_bsd_name = base::mac::GetValueFromDictionary<CFStringRef>(
49 dict, kDADiskDescriptionMediaBSDNameKey);
50 if (device_bsd_name && bsd_name)
51 *bsd_name = base::SysCFStringRefToUTF8(device_bsd_name);
53 CFURLRef url = base::mac::GetValueFromDictionary<CFURLRef>(
54 dict, kDADiskDescriptionVolumePathKey);
55 NSURL* nsurl = base::mac::CFToNSCast(url);
56 base::FilePath location = base::mac::NSStringToFilePath([nsurl path]);
57 CFNumberRef size_number =
58 base::mac::GetValueFromDictionary<CFNumberRef>(
59 dict, kDADiskDescriptionMediaSizeKey);
60 uint64 size_in_bytes = 0;
62 CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes);
64 string16 vendor = GetUTF16FromDictionary(
65 dict, kDADiskDescriptionDeviceVendorKey);
66 string16 model = GetUTF16FromDictionary(
67 dict, kDADiskDescriptionDeviceModelKey);
68 string16 label = GetUTF16FromDictionary(
69 dict, kDADiskDescriptionVolumeNameKey);
71 CFUUIDRef uuid = base::mac::GetValueFromDictionary<CFUUIDRef>(
72 dict, kDADiskDescriptionVolumeUUIDKey);
73 std::string unique_id;
75 base::ScopedCFTypeRef<CFStringRef> uuid_string(
76 CFUUIDCreateString(NULL, uuid));
77 if (uuid_string.get())
78 unique_id = base::SysCFStringRefToUTF8(uuid_string);
80 if (unique_id.empty()) {
81 string16 revision = GetUTF16FromDictionary(
82 dict, kDADiskDescriptionDeviceRevisionKey);
83 string16 unique_id2 = vendor;
84 unique_id2 = JoinName(unique_id2, model);
85 unique_id2 = JoinName(unique_id2, revision);
86 unique_id = UTF16ToUTF8(unique_id2);
89 CFBooleanRef is_removable_ref =
90 base::mac::GetValueFromDictionary<CFBooleanRef>(
91 dict, kDADiskDescriptionMediaRemovableKey);
92 bool is_removable = is_removable_ref && CFBooleanGetValue(is_removable_ref);
93 // Checking for DCIM only matters on removable devices.
94 bool has_dcim = is_removable && MediaStorageUtil::HasDcim(location);
95 StorageInfo::Type device_type = GetDeviceType(is_removable, has_dcim);
96 std::string device_id;
97 if (!unique_id.empty())
98 device_id = StorageInfo::MakeDeviceId(device_type, unique_id);
100 return StorageInfo(device_id, string16(), location.value(), label, vendor,
101 model, size_in_bytes);
104 void GetDiskInfoAndUpdateOnFileThread(
105 const base::WeakPtr<StorageMonitorMac>& monitor,
106 base::ScopedCFTypeRef<CFDictionaryRef> dict,
107 StorageMonitorMac::UpdateType update_type) {
108 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
110 std::string bsd_name;
111 StorageInfo info = BuildStorageInfo(dict, &bsd_name);
113 content::BrowserThread::PostTask(
114 content::BrowserThread::UI,
116 base::Bind(&StorageMonitorMac::UpdateDisk,
123 struct EjectDiskOptions {
124 std::string bsd_name;
125 base::Callback<void(StorageMonitor::EjectStatus)> callback;
126 base::ScopedCFTypeRef<DADiskRef> disk;
129 void PostEjectCallback(DADiskRef disk,
130 DADissenterRef dissenter,
132 scoped_ptr<EjectDiskOptions> options_deleter(
133 static_cast<EjectDiskOptions*>(context));
135 options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
139 options_deleter->callback.Run(StorageMonitor::EJECT_OK);
142 void PostUnmountCallback(DADiskRef disk,
143 DADissenterRef dissenter,
145 scoped_ptr<EjectDiskOptions> options_deleter(
146 static_cast<EjectDiskOptions*>(context));
148 options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
152 DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault,
153 PostEjectCallback, options_deleter.release());
156 void EjectDisk(EjectDiskOptions* options) {
157 DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole,
158 PostUnmountCallback, options);
163 StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) {
166 StorageMonitorMac::~StorageMonitorMac() {
167 if (session_.get()) {
168 DASessionUnscheduleFromRunLoop(
169 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
173 void StorageMonitorMac::Init() {
174 session_.reset(DASessionCreate(NULL));
176 // Register for callbacks for attached, changed, and removed devices.
177 // This will send notifications for existing devices too.
178 DARegisterDiskAppearedCallback(
180 kDADiskDescriptionMatchVolumeMountable,
181 DiskAppearedCallback,
183 DARegisterDiskDisappearedCallback(
185 kDADiskDescriptionMatchVolumeMountable,
186 DiskDisappearedCallback,
188 DARegisterDiskDescriptionChangedCallback(
190 kDADiskDescriptionMatchVolumeMountable,
191 kDADiskDescriptionWatchVolumePath,
192 DiskDescriptionChangedCallback,
195 DASessionScheduleWithRunLoop(
196 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
198 if (base::mac::IsOSLionOrLater()) {
199 image_capture_device_manager_.reset(new ImageCaptureDeviceManager);
200 image_capture_device_manager_->SetNotifications(receiver());
204 void StorageMonitorMac::UpdateDisk(
205 const std::string& bsd_name,
206 const StorageInfo& info,
207 UpdateType update_type) {
208 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
210 pending_disk_updates_--;
211 bool initialization_complete = false;
212 if (!IsInitialized() && pending_disk_updates_ == 0)
213 initialization_complete = true;
215 if (info.device_id().empty() || bsd_name.empty()) {
216 if (initialization_complete)
221 std::map<std::string, StorageInfo>::iterator it =
222 disk_info_map_.find(bsd_name);
223 if (it != disk_info_map_.end()) {
224 // If an attached notification was previously posted then post a detached
225 // notification now. This is used for devices that are being removed or
226 // devices that have changed.
227 if (ShouldPostNotificationForDisk(it->second)) {
228 receiver()->ProcessDetach(it->second.device_id());
232 if (update_type == UPDATE_DEVICE_REMOVED) {
233 if (it != disk_info_map_.end())
234 disk_info_map_.erase(it);
236 disk_info_map_[bsd_name] = info;
237 MediaStorageUtil::RecordDeviceInfoHistogram(true, info.device_id(),
238 info.storage_label());
239 if (ShouldPostNotificationForDisk(info))
240 receiver()->ProcessAttach(info);
243 // We're not really honestly sure we're done, but this looks the best we
244 // can do. Any misses should go out through notifications.
245 if (initialization_complete)
249 bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path,
250 StorageInfo* device_info) const {
253 if (!path.IsAbsolute())
256 base::FilePath current = path;
257 const base::FilePath root(base::FilePath::kSeparators);
258 while (current != root) {
260 if (FindDiskWithMountPoint(current, &info)) {
264 current = current.DirName();
270 void StorageMonitorMac::EjectDevice(
271 const std::string& device_id,
272 base::Callback<void(EjectStatus)> callback) {
273 StorageInfo::Type type;
275 if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) {
276 callback.Run(EJECT_FAILURE);
280 if (type == StorageInfo::MAC_IMAGE_CAPTURE &&
281 image_capture_device_manager_.get()) {
282 image_capture_device_manager_->EjectDevice(uuid, callback);
286 std::string bsd_name;
287 for (std::map<std::string, StorageInfo>::iterator
288 it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
289 if (it->second.device_id() == device_id) {
290 bsd_name = it->first;
291 disk_info_map_.erase(it);
296 if (bsd_name.empty()) {
297 callback.Run(EJECT_NO_SUCH_DEVICE);
301 receiver()->ProcessDetach(device_id);
303 base::ScopedCFTypeRef<DADiskRef> disk(
304 DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str()));
306 callback.Run(StorageMonitor::EJECT_FAILURE);
309 // Get the reference to the full disk for ejecting.
310 disk.reset(DADiskCopyWholeDisk(disk));
312 callback.Run(StorageMonitor::EJECT_FAILURE);
316 EjectDiskOptions* options = new EjectDiskOptions;
317 options->bsd_name = bsd_name;
318 options->callback = callback;
319 options->disk.reset(disk.release());
320 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
321 base::Bind(EjectDisk, options));
325 void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) {
326 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
327 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED);
331 void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) {
332 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
333 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED);
337 void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk,
340 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
341 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED);
344 void StorageMonitorMac::GetDiskInfoAndUpdate(
346 StorageMonitorMac::UpdateType update_type) {
347 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
349 pending_disk_updates_++;
351 base::ScopedCFTypeRef<CFDictionaryRef> dict(DADiskCopyDescription(disk));
352 content::BrowserThread::PostTask(
353 content::BrowserThread::FILE,
355 base::Bind(GetDiskInfoAndUpdateOnFileThread,
356 AsWeakPtr(), dict, update_type));
360 bool StorageMonitorMac::ShouldPostNotificationForDisk(
361 const StorageInfo& info) const {
362 // Only post notifications about disks that have no empty fields and
363 // are removable. Also exclude disk images (DMGs).
364 return !info.device_id().empty() &&
365 !info.location().empty() &&
366 info.model_name() != ASCIIToUTF16(kDiskImageModelName) &&
367 StorageInfo::IsMassStorageDevice(info.device_id());
370 bool StorageMonitorMac::FindDiskWithMountPoint(
371 const base::FilePath& mount_point,
372 StorageInfo* info) const {
373 for (std::map<std::string, StorageInfo>::const_iterator
374 it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
375 if (it->second.location() == mount_point.value()) {
383 StorageMonitor* StorageMonitor::Create() {
384 return new StorageMonitorMac();