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 base::string16 GetUTF16FromDictionary(CFDictionaryRef dictionary,
23 base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key);
25 return base::string16();
26 return base::SysCFStringRefToUTF16(value);
29 base::string16 JoinName(const base::string16& name,
30 const base::string16& addition) {
35 return name + static_cast<base::char16>(' ') + addition;
38 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
40 return StorageInfo::FIXED_MASS_STORAGE;
42 return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
43 return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
46 StorageInfo BuildStorageInfo(
47 CFDictionaryRef dict, std::string* bsd_name) {
48 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
50 CFStringRef device_bsd_name = base::mac::GetValueFromDictionary<CFStringRef>(
51 dict, kDADiskDescriptionMediaBSDNameKey);
52 if (device_bsd_name && bsd_name)
53 *bsd_name = base::SysCFStringRefToUTF8(device_bsd_name);
55 CFURLRef url = base::mac::GetValueFromDictionary<CFURLRef>(
56 dict, kDADiskDescriptionVolumePathKey);
57 NSURL* nsurl = base::mac::CFToNSCast(url);
58 base::FilePath location = base::mac::NSStringToFilePath([nsurl path]);
59 CFNumberRef size_number =
60 base::mac::GetValueFromDictionary<CFNumberRef>(
61 dict, kDADiskDescriptionMediaSizeKey);
62 uint64 size_in_bytes = 0;
64 CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes);
66 base::string16 vendor = GetUTF16FromDictionary(
67 dict, kDADiskDescriptionDeviceVendorKey);
68 base::string16 model = GetUTF16FromDictionary(
69 dict, kDADiskDescriptionDeviceModelKey);
70 base::string16 label = GetUTF16FromDictionary(
71 dict, kDADiskDescriptionVolumeNameKey);
73 CFUUIDRef uuid = base::mac::GetValueFromDictionary<CFUUIDRef>(
74 dict, kDADiskDescriptionVolumeUUIDKey);
75 std::string unique_id;
77 base::ScopedCFTypeRef<CFStringRef> uuid_string(
78 CFUUIDCreateString(NULL, uuid));
79 if (uuid_string.get())
80 unique_id = base::SysCFStringRefToUTF8(uuid_string);
82 if (unique_id.empty()) {
83 base::string16 revision = GetUTF16FromDictionary(
84 dict, kDADiskDescriptionDeviceRevisionKey);
85 base::string16 unique_id2 = vendor;
86 unique_id2 = JoinName(unique_id2, model);
87 unique_id2 = JoinName(unique_id2, revision);
88 unique_id = base::UTF16ToUTF8(unique_id2);
91 CFBooleanRef is_removable_ref =
92 base::mac::GetValueFromDictionary<CFBooleanRef>(
93 dict, kDADiskDescriptionMediaRemovableKey);
94 bool is_removable = is_removable_ref && CFBooleanGetValue(is_removable_ref);
95 // Checking for DCIM only matters on removable devices.
96 bool has_dcim = is_removable && MediaStorageUtil::HasDcim(location);
97 StorageInfo::Type device_type = GetDeviceType(is_removable, has_dcim);
98 std::string device_id;
99 if (!unique_id.empty())
100 device_id = StorageInfo::MakeDeviceId(device_type, unique_id);
102 return StorageInfo(device_id, base::string16(), location.value(), label,
103 vendor, model, size_in_bytes);
106 void GetDiskInfoAndUpdateOnFileThread(
107 const base::WeakPtr<StorageMonitorMac>& monitor,
108 base::ScopedCFTypeRef<CFDictionaryRef> dict,
109 StorageMonitorMac::UpdateType update_type) {
110 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
112 std::string bsd_name;
113 StorageInfo info = BuildStorageInfo(dict, &bsd_name);
115 content::BrowserThread::PostTask(
116 content::BrowserThread::UI,
118 base::Bind(&StorageMonitorMac::UpdateDisk,
125 struct EjectDiskOptions {
126 std::string bsd_name;
127 base::Callback<void(StorageMonitor::EjectStatus)> callback;
128 base::ScopedCFTypeRef<DADiskRef> disk;
131 void PostEjectCallback(DADiskRef disk,
132 DADissenterRef dissenter,
134 scoped_ptr<EjectDiskOptions> options_deleter(
135 static_cast<EjectDiskOptions*>(context));
137 options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
141 options_deleter->callback.Run(StorageMonitor::EJECT_OK);
144 void PostUnmountCallback(DADiskRef disk,
145 DADissenterRef dissenter,
147 scoped_ptr<EjectDiskOptions> options_deleter(
148 static_cast<EjectDiskOptions*>(context));
150 options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
154 DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault,
155 PostEjectCallback, options_deleter.release());
158 void EjectDisk(EjectDiskOptions* options) {
159 DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole,
160 PostUnmountCallback, options);
165 StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) {
168 StorageMonitorMac::~StorageMonitorMac() {
169 if (session_.get()) {
170 DASessionUnscheduleFromRunLoop(
171 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
175 void StorageMonitorMac::Init() {
176 session_.reset(DASessionCreate(NULL));
178 // Register for callbacks for attached, changed, and removed devices.
179 // This will send notifications for existing devices too.
180 DARegisterDiskAppearedCallback(
182 kDADiskDescriptionMatchVolumeMountable,
183 DiskAppearedCallback,
185 DARegisterDiskDisappearedCallback(
187 kDADiskDescriptionMatchVolumeMountable,
188 DiskDisappearedCallback,
190 DARegisterDiskDescriptionChangedCallback(
192 kDADiskDescriptionMatchVolumeMountable,
193 kDADiskDescriptionWatchVolumePath,
194 DiskDescriptionChangedCallback,
197 DASessionScheduleWithRunLoop(
198 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
200 if (base::mac::IsOSLionOrLater()) {
201 image_capture_device_manager_.reset(new ImageCaptureDeviceManager);
202 image_capture_device_manager_->SetNotifications(receiver());
206 void StorageMonitorMac::UpdateDisk(
207 const std::string& bsd_name,
208 const StorageInfo& info,
209 UpdateType update_type) {
210 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
212 pending_disk_updates_--;
213 bool initialization_complete = false;
214 if (!IsInitialized() && pending_disk_updates_ == 0)
215 initialization_complete = true;
217 if (info.device_id().empty() || bsd_name.empty()) {
218 if (initialization_complete)
223 std::map<std::string, StorageInfo>::iterator it =
224 disk_info_map_.find(bsd_name);
225 if (it != disk_info_map_.end()) {
226 // If an attached notification was previously posted then post a detached
227 // notification now. This is used for devices that are being removed or
228 // devices that have changed.
229 if (ShouldPostNotificationForDisk(it->second)) {
230 receiver()->ProcessDetach(it->second.device_id());
234 if (update_type == UPDATE_DEVICE_REMOVED) {
235 if (it != disk_info_map_.end())
236 disk_info_map_.erase(it);
238 disk_info_map_[bsd_name] = info;
239 MediaStorageUtil::RecordDeviceInfoHistogram(true, info.device_id(),
240 info.storage_label());
241 if (ShouldPostNotificationForDisk(info))
242 receiver()->ProcessAttach(info);
245 // We're not really honestly sure we're done, but this looks the best we
246 // can do. Any misses should go out through notifications.
247 if (initialization_complete)
251 bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path,
252 StorageInfo* device_info) const {
255 if (!path.IsAbsolute())
258 base::FilePath current = path;
259 const base::FilePath root(base::FilePath::kSeparators);
260 while (current != root) {
262 if (FindDiskWithMountPoint(current, &info)) {
266 current = current.DirName();
272 void StorageMonitorMac::EjectDevice(
273 const std::string& device_id,
274 base::Callback<void(EjectStatus)> callback) {
275 StorageInfo::Type type;
277 if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) {
278 callback.Run(EJECT_FAILURE);
282 if (type == StorageInfo::MAC_IMAGE_CAPTURE &&
283 image_capture_device_manager_.get()) {
284 image_capture_device_manager_->EjectDevice(uuid, callback);
288 std::string bsd_name;
289 for (std::map<std::string, StorageInfo>::iterator
290 it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
291 if (it->second.device_id() == device_id) {
292 bsd_name = it->first;
293 disk_info_map_.erase(it);
298 if (bsd_name.empty()) {
299 callback.Run(EJECT_NO_SUCH_DEVICE);
303 receiver()->ProcessDetach(device_id);
305 base::ScopedCFTypeRef<DADiskRef> disk(
306 DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str()));
308 callback.Run(StorageMonitor::EJECT_FAILURE);
311 // Get the reference to the full disk for ejecting.
312 disk.reset(DADiskCopyWholeDisk(disk));
314 callback.Run(StorageMonitor::EJECT_FAILURE);
318 EjectDiskOptions* options = new EjectDiskOptions;
319 options->bsd_name = bsd_name;
320 options->callback = callback;
321 options->disk.reset(disk.release());
322 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
323 base::Bind(EjectDisk, options));
327 void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) {
328 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
329 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED);
333 void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) {
334 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
335 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED);
339 void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk,
342 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
343 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED);
346 void StorageMonitorMac::GetDiskInfoAndUpdate(
348 StorageMonitorMac::UpdateType update_type) {
349 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
351 pending_disk_updates_++;
353 base::ScopedCFTypeRef<CFDictionaryRef> dict(DADiskCopyDescription(disk));
354 content::BrowserThread::PostTask(
355 content::BrowserThread::FILE,
357 base::Bind(GetDiskInfoAndUpdateOnFileThread,
358 AsWeakPtr(), dict, update_type));
362 bool StorageMonitorMac::ShouldPostNotificationForDisk(
363 const StorageInfo& info) const {
364 // Only post notifications about disks that have no empty fields and
365 // are removable. Also exclude disk images (DMGs).
366 return !info.device_id().empty() &&
367 !info.location().empty() &&
368 info.model_name() != base::ASCIIToUTF16(kDiskImageModelName) &&
369 StorageInfo::IsMassStorageDevice(info.device_id());
372 bool StorageMonitorMac::FindDiskWithMountPoint(
373 const base::FilePath& mount_point,
374 StorageInfo* info) const {
375 for (std::map<std::string, StorageInfo>::const_iterator
376 it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
377 if (it->second.location() == mount_point.value()) {
385 StorageMonitor* StorageMonitor::Create() {
386 return new StorageMonitorMac();