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.
5 #include "components/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 "components/storage_monitor/image_capture_device_manager.h"
12 #include "components/storage_monitor/media_storage_util.h"
13 #include "components/storage_monitor/storage_info.h"
14 #include "content/public/browser/browser_thread.h"
16 namespace storage_monitor {
20 const char kDiskImageModelName[] = "Disk Image";
22 base::string16 GetUTF16FromDictionary(CFDictionaryRef dictionary,
25 base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key);
27 return base::string16();
28 return base::SysCFStringRefToUTF16(value);
31 base::string16 JoinName(const base::string16& name,
32 const base::string16& addition) {
37 return name + static_cast<base::char16>(' ') + addition;
40 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
42 return StorageInfo::FIXED_MASS_STORAGE;
44 return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
45 return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
48 StorageInfo BuildStorageInfo(
49 CFDictionaryRef dict, std::string* bsd_name) {
50 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
52 CFStringRef device_bsd_name = base::mac::GetValueFromDictionary<CFStringRef>(
53 dict, kDADiskDescriptionMediaBSDNameKey);
54 if (device_bsd_name && bsd_name)
55 *bsd_name = base::SysCFStringRefToUTF8(device_bsd_name);
57 CFURLRef url = base::mac::GetValueFromDictionary<CFURLRef>(
58 dict, kDADiskDescriptionVolumePathKey);
59 NSURL* nsurl = base::mac::CFToNSCast(url);
60 base::FilePath location = base::mac::NSStringToFilePath([nsurl path]);
61 CFNumberRef size_number =
62 base::mac::GetValueFromDictionary<CFNumberRef>(
63 dict, kDADiskDescriptionMediaSizeKey);
64 uint64 size_in_bytes = 0;
66 CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes);
68 base::string16 vendor = GetUTF16FromDictionary(
69 dict, kDADiskDescriptionDeviceVendorKey);
70 base::string16 model = GetUTF16FromDictionary(
71 dict, kDADiskDescriptionDeviceModelKey);
72 base::string16 label = GetUTF16FromDictionary(
73 dict, kDADiskDescriptionVolumeNameKey);
75 CFUUIDRef uuid = base::mac::GetValueFromDictionary<CFUUIDRef>(
76 dict, kDADiskDescriptionVolumeUUIDKey);
77 std::string unique_id;
79 base::ScopedCFTypeRef<CFStringRef> uuid_string(
80 CFUUIDCreateString(NULL, uuid));
81 if (uuid_string.get())
82 unique_id = base::SysCFStringRefToUTF8(uuid_string);
84 if (unique_id.empty()) {
85 base::string16 revision = GetUTF16FromDictionary(
86 dict, kDADiskDescriptionDeviceRevisionKey);
87 base::string16 unique_id2 = vendor;
88 unique_id2 = JoinName(unique_id2, model);
89 unique_id2 = JoinName(unique_id2, revision);
90 unique_id = base::UTF16ToUTF8(unique_id2);
93 CFBooleanRef is_removable_ref =
94 base::mac::GetValueFromDictionary<CFBooleanRef>(
95 dict, kDADiskDescriptionMediaRemovableKey);
96 bool is_removable = is_removable_ref && CFBooleanGetValue(is_removable_ref);
97 // Checking for DCIM only matters on removable devices.
98 bool has_dcim = is_removable && MediaStorageUtil::HasDcim(location);
99 StorageInfo::Type device_type = GetDeviceType(is_removable, has_dcim);
100 std::string device_id;
101 if (!unique_id.empty())
102 device_id = StorageInfo::MakeDeviceId(device_type, unique_id);
104 return StorageInfo(device_id, location.value(), label, vendor, model,
108 void GetDiskInfoAndUpdateOnFileThread(
109 const base::WeakPtr<StorageMonitorMac>& monitor,
110 base::ScopedCFTypeRef<CFDictionaryRef> dict,
111 StorageMonitorMac::UpdateType update_type) {
112 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
114 std::string bsd_name;
115 StorageInfo info = BuildStorageInfo(dict, &bsd_name);
117 content::BrowserThread::PostTask(
118 content::BrowserThread::UI,
120 base::Bind(&StorageMonitorMac::UpdateDisk,
127 struct EjectDiskOptions {
128 std::string bsd_name;
129 base::Callback<void(StorageMonitor::EjectStatus)> callback;
130 base::ScopedCFTypeRef<DADiskRef> disk;
133 void PostEjectCallback(DADiskRef disk,
134 DADissenterRef dissenter,
136 scoped_ptr<EjectDiskOptions> options_deleter(
137 static_cast<EjectDiskOptions*>(context));
139 options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
143 options_deleter->callback.Run(StorageMonitor::EJECT_OK);
146 void PostUnmountCallback(DADiskRef disk,
147 DADissenterRef dissenter,
149 scoped_ptr<EjectDiskOptions> options_deleter(
150 static_cast<EjectDiskOptions*>(context));
152 options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
156 DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault,
157 PostEjectCallback, options_deleter.release());
160 void EjectDisk(EjectDiskOptions* options) {
161 DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole,
162 PostUnmountCallback, options);
167 StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) {
170 StorageMonitorMac::~StorageMonitorMac() {
171 if (session_.get()) {
172 DASessionUnscheduleFromRunLoop(
173 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
177 void StorageMonitorMac::Init() {
178 session_.reset(DASessionCreate(NULL));
180 // Register for callbacks for attached, changed, and removed devices.
181 // This will send notifications for existing devices too.
182 DARegisterDiskAppearedCallback(
184 kDADiskDescriptionMatchVolumeMountable,
185 DiskAppearedCallback,
187 DARegisterDiskDisappearedCallback(
189 kDADiskDescriptionMatchVolumeMountable,
190 DiskDisappearedCallback,
192 DARegisterDiskDescriptionChangedCallback(
194 kDADiskDescriptionMatchVolumeMountable,
195 kDADiskDescriptionWatchVolumePath,
196 DiskDescriptionChangedCallback,
199 DASessionScheduleWithRunLoop(
200 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
202 if (base::mac::IsOSLionOrLater()) {
203 image_capture_device_manager_.reset(new ImageCaptureDeviceManager);
204 image_capture_device_manager_->SetNotifications(receiver());
208 void StorageMonitorMac::UpdateDisk(
209 const std::string& bsd_name,
210 const StorageInfo& info,
211 UpdateType update_type) {
212 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
214 pending_disk_updates_--;
215 bool initialization_complete = false;
216 if (!IsInitialized() && pending_disk_updates_ == 0)
217 initialization_complete = true;
219 if (info.device_id().empty() || bsd_name.empty()) {
220 if (initialization_complete)
225 std::map<std::string, StorageInfo>::iterator it =
226 disk_info_map_.find(bsd_name);
227 if (it != disk_info_map_.end()) {
228 // If an attached notification was previously posted then post a detached
229 // notification now. This is used for devices that are being removed or
230 // devices that have changed.
231 if (ShouldPostNotificationForDisk(it->second)) {
232 receiver()->ProcessDetach(it->second.device_id());
236 if (update_type == UPDATE_DEVICE_REMOVED) {
237 if (it != disk_info_map_.end())
238 disk_info_map_.erase(it);
240 disk_info_map_[bsd_name] = info;
241 MediaStorageUtil::RecordDeviceInfoHistogram(true, info.device_id(),
242 info.storage_label());
243 if (ShouldPostNotificationForDisk(info))
244 receiver()->ProcessAttach(info);
247 // We're not really honestly sure we're done, but this looks the best we
248 // can do. Any misses should go out through notifications.
249 if (initialization_complete)
253 bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path,
254 StorageInfo* device_info) const {
257 if (!path.IsAbsolute())
260 base::FilePath current = path;
261 const base::FilePath root(base::FilePath::kSeparators);
262 while (current != root) {
264 if (FindDiskWithMountPoint(current, &info)) {
268 current = current.DirName();
274 void StorageMonitorMac::EjectDevice(
275 const std::string& device_id,
276 base::Callback<void(EjectStatus)> callback) {
277 StorageInfo::Type type;
279 if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) {
280 callback.Run(EJECT_FAILURE);
284 if (type == StorageInfo::MAC_IMAGE_CAPTURE &&
285 image_capture_device_manager_.get()) {
286 image_capture_device_manager_->EjectDevice(uuid, callback);
290 std::string bsd_name;
291 for (std::map<std::string, StorageInfo>::iterator
292 it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
293 if (it->second.device_id() == device_id) {
294 bsd_name = it->first;
295 disk_info_map_.erase(it);
300 if (bsd_name.empty()) {
301 callback.Run(EJECT_NO_SUCH_DEVICE);
305 receiver()->ProcessDetach(device_id);
307 base::ScopedCFTypeRef<DADiskRef> disk(
308 DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str()));
310 callback.Run(StorageMonitor::EJECT_FAILURE);
313 // Get the reference to the full disk for ejecting.
314 disk.reset(DADiskCopyWholeDisk(disk));
316 callback.Run(StorageMonitor::EJECT_FAILURE);
320 EjectDiskOptions* options = new EjectDiskOptions;
321 options->bsd_name = bsd_name;
322 options->callback = callback;
323 options->disk.reset(disk.release());
324 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
325 base::Bind(EjectDisk, options));
329 void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) {
330 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
331 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED);
335 void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) {
336 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
337 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED);
341 void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk,
344 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
345 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED);
348 void StorageMonitorMac::GetDiskInfoAndUpdate(
350 StorageMonitorMac::UpdateType update_type) {
351 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
353 pending_disk_updates_++;
355 base::ScopedCFTypeRef<CFDictionaryRef> dict(DADiskCopyDescription(disk));
356 content::BrowserThread::PostTask(
357 content::BrowserThread::FILE,
359 base::Bind(GetDiskInfoAndUpdateOnFileThread,
360 AsWeakPtr(), dict, update_type));
364 bool StorageMonitorMac::ShouldPostNotificationForDisk(
365 const StorageInfo& info) const {
366 // Only post notifications about disks that have no empty fields and
367 // are removable. Also exclude disk images (DMGs).
368 return !info.device_id().empty() &&
369 !info.location().empty() &&
370 info.model_name() != base::ASCIIToUTF16(kDiskImageModelName) &&
371 StorageInfo::IsMassStorageDevice(info.device_id());
374 bool StorageMonitorMac::FindDiskWithMountPoint(
375 const base::FilePath& mount_point,
376 StorageInfo* info) const {
377 for (std::map<std::string, StorageInfo>::const_iterator
378 it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
379 if (it->second.location() == mount_point.value()) {
387 StorageMonitor* StorageMonitor::CreateInternal() {
388 return new StorageMonitorMac();
391 } // namespace storage_monitor