Restore simple fling status bookkeeping
[chromium-blink-merge.git] / components / storage_monitor / storage_monitor_mac.mm
blob22a884b46094ae0afa5dafbcf5551a3121464ff6
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 {
18 namespace {
20 const char kDiskImageModelName[] = "Disk Image";
22 base::string16 GetUTF16FromDictionary(CFDictionaryRef dictionary,
23                                       CFStringRef key) {
24   CFStringRef value =
25       base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key);
26   if (!value)
27     return base::string16();
28   return base::SysCFStringRefToUTF16(value);
31 base::string16 JoinName(const base::string16& name,
32                         const base::string16& addition) {
33   if (addition.empty())
34     return name;
35   if (name.empty())
36     return addition;
37   return name + static_cast<base::char16>(' ') + addition;
40 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
41   if (!is_removable)
42     return StorageInfo::FIXED_MASS_STORAGE;
43   if (has_dcim)
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_CURRENTLY_ON(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;
65   if (size_number)
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;
78   if (uuid) {
79     base::ScopedCFTypeRef<CFStringRef> uuid_string(
80         CFUUIDCreateString(NULL, uuid));
81     if (uuid_string.get())
82       unique_id = base::SysCFStringRefToUTF8(uuid_string);
83   }
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);
91   }
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,
105                      size_in_bytes);
108 void GetDiskInfoAndUpdateOnFileThread(
109     const base::WeakPtr<StorageMonitorMac>& monitor,
110     base::ScopedCFTypeRef<CFDictionaryRef> dict,
111     StorageMonitorMac::UpdateType update_type) {
112   DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
114   std::string bsd_name;
115   StorageInfo info = BuildStorageInfo(dict, &bsd_name);
117   content::BrowserThread::PostTask(
118       content::BrowserThread::UI,
119       FROM_HERE,
120       base::Bind(&StorageMonitorMac::UpdateDisk,
121                  monitor,
122                  bsd_name,
123                  info,
124                  update_type));
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,
135                        void* context) {
136   scoped_ptr<EjectDiskOptions> options_deleter(
137       static_cast<EjectDiskOptions*>(context));
138   if (dissenter) {
139     options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
140     return;
141   }
143   options_deleter->callback.Run(StorageMonitor::EJECT_OK);
146 void PostUnmountCallback(DADiskRef disk,
147                          DADissenterRef dissenter,
148                          void* context) {
149   scoped_ptr<EjectDiskOptions> options_deleter(
150       static_cast<EjectDiskOptions*>(context));
151   if (dissenter) {
152     options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
153     return;
154   }
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);
165 }  // namespace
167 StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) {
170 StorageMonitorMac::~StorageMonitorMac() {
171   if (session_.get()) {
172     DASessionUnscheduleFromRunLoop(
173         session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
174   }
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(
183       session_,
184       kDADiskDescriptionMatchVolumeMountable,
185       DiskAppearedCallback,
186       this);
187   DARegisterDiskDisappearedCallback(
188       session_,
189       kDADiskDescriptionMatchVolumeMountable,
190       DiskDisappearedCallback,
191       this);
192   DARegisterDiskDescriptionChangedCallback(
193       session_,
194       kDADiskDescriptionMatchVolumeMountable,
195       kDADiskDescriptionWatchVolumePath,
196       DiskDescriptionChangedCallback,
197       this);
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());
205   }
208 void StorageMonitorMac::UpdateDisk(
209     const std::string& bsd_name,
210     const StorageInfo& info,
211     UpdateType update_type) {
212   DCHECK_CURRENTLY_ON(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)
221       MarkInitialized();
222     return;
223   }
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());
233     }
234   }
236   if (update_type == UPDATE_DEVICE_REMOVED) {
237     if (it != disk_info_map_.end())
238       disk_info_map_.erase(it);
239   } else {
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);
245   }
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)
250     MarkInitialized();
253 bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path,
254                                               StorageInfo* device_info) const {
255   DCHECK(device_info);
257   if (!path.IsAbsolute())
258     return false;
260   base::FilePath current = path;
261   const base::FilePath root(base::FilePath::kSeparators);
262   while (current != root) {
263     StorageInfo info;
264     if (FindDiskWithMountPoint(current, &info)) {
265       *device_info = info;
266       return true;
267     }
268     current = current.DirName();
269   }
271   return false;
274 void StorageMonitorMac::EjectDevice(
275       const std::string& device_id,
276       base::Callback<void(EjectStatus)> callback) {
277   StorageInfo::Type type;
278   std::string uuid;
279   if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) {
280     callback.Run(EJECT_FAILURE);
281     return;
282   }
284   if (type == StorageInfo::MAC_IMAGE_CAPTURE &&
285       image_capture_device_manager_.get()) {
286     image_capture_device_manager_->EjectDevice(uuid, callback);
287     return;
288   }
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);
296       break;
297     }
298   }
300   if (bsd_name.empty()) {
301     callback.Run(EJECT_NO_SUCH_DEVICE);
302     return;
303   }
305   receiver()->ProcessDetach(device_id);
307   base::ScopedCFTypeRef<DADiskRef> disk(
308       DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str()));
309   if (!disk.get()) {
310     callback.Run(StorageMonitor::EJECT_FAILURE);
311     return;
312   }
313   // Get the reference to the full disk for ejecting.
314   disk.reset(DADiskCopyWholeDisk(disk));
315   if (!disk.get()) {
316     callback.Run(StorageMonitor::EJECT_FAILURE);
317     return;
318   }
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));
328 // static
329 void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) {
330   StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
331   monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED);
334 // static
335 void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) {
336   StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
337   monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED);
340 // static
341 void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk,
342                                                        CFArrayRef keys,
343                                                        void *context) {
344   StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
345   monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED);
348 void StorageMonitorMac::GetDiskInfoAndUpdate(
349     DADiskRef disk,
350     StorageMonitorMac::UpdateType update_type) {
351   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
353   pending_disk_updates_++;
355   base::ScopedCFTypeRef<CFDictionaryRef> dict(DADiskCopyDescription(disk));
356   content::BrowserThread::PostTask(
357       content::BrowserThread::FILE,
358       FROM_HERE,
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()) {
380       *info = it->second;
381       return true;
382     }
383   }
384   return false;
387 StorageMonitor* StorageMonitor::CreateInternal() {
388   return new StorageMonitorMac();
391 }  // namespace storage_monitor