Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / components / storage_monitor / storage_monitor_mac.mm
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.
4
5 #include "components/storage_monitor/storage_monitor_mac.h"
6
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"
15
16 namespace {
17
18 const char kDiskImageModelName[] = "Disk Image";
19
20 base::string16 GetUTF16FromDictionary(CFDictionaryRef dictionary,
21                                       CFStringRef key) {
22   CFStringRef value =
23       base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key);
24   if (!value)
25     return base::string16();
26   return base::SysCFStringRefToUTF16(value);
27 }
28
29 base::string16 JoinName(const base::string16& name,
30                         const base::string16& addition) {
31   if (addition.empty())
32     return name;
33   if (name.empty())
34     return addition;
35   return name + static_cast<base::char16>(' ') + addition;
36 }
37
38 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
39   if (!is_removable)
40     return StorageInfo::FIXED_MASS_STORAGE;
41   if (has_dcim)
42     return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
43   return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
44 }
45
46 StorageInfo BuildStorageInfo(
47     CFDictionaryRef dict, std::string* bsd_name) {
48   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
49
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);
54
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;
63   if (size_number)
64     CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes);
65
66   base::string16 vendor = GetUTF16FromDictionary(
67       dict, kDADiskDescriptionDeviceVendorKey);
68   base::string16 model = GetUTF16FromDictionary(
69       dict, kDADiskDescriptionDeviceModelKey);
70   base::string16 label = GetUTF16FromDictionary(
71       dict, kDADiskDescriptionVolumeNameKey);
72
73   CFUUIDRef uuid = base::mac::GetValueFromDictionary<CFUUIDRef>(
74       dict, kDADiskDescriptionVolumeUUIDKey);
75   std::string unique_id;
76   if (uuid) {
77     base::ScopedCFTypeRef<CFStringRef> uuid_string(
78         CFUUIDCreateString(NULL, uuid));
79     if (uuid_string.get())
80       unique_id = base::SysCFStringRefToUTF8(uuid_string);
81   }
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);
89   }
90
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);
101
102   return StorageInfo(device_id, base::string16(), location.value(), label,
103                      vendor, model, size_in_bytes);
104 }
105
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));
111
112   std::string bsd_name;
113   StorageInfo info = BuildStorageInfo(dict, &bsd_name);
114
115   content::BrowserThread::PostTask(
116       content::BrowserThread::UI,
117       FROM_HERE,
118       base::Bind(&StorageMonitorMac::UpdateDisk,
119                  monitor,
120                  bsd_name,
121                  info,
122                  update_type));
123 }
124
125 struct EjectDiskOptions {
126   std::string bsd_name;
127   base::Callback<void(StorageMonitor::EjectStatus)> callback;
128   base::ScopedCFTypeRef<DADiskRef> disk;
129 };
130
131 void PostEjectCallback(DADiskRef disk,
132                        DADissenterRef dissenter,
133                        void* context) {
134   scoped_ptr<EjectDiskOptions> options_deleter(
135       static_cast<EjectDiskOptions*>(context));
136   if (dissenter) {
137     options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
138     return;
139   }
140
141   options_deleter->callback.Run(StorageMonitor::EJECT_OK);
142 }
143
144 void PostUnmountCallback(DADiskRef disk,
145                          DADissenterRef dissenter,
146                          void* context) {
147   scoped_ptr<EjectDiskOptions> options_deleter(
148       static_cast<EjectDiskOptions*>(context));
149   if (dissenter) {
150     options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
151     return;
152   }
153
154   DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault,
155               PostEjectCallback, options_deleter.release());
156 }
157
158 void EjectDisk(EjectDiskOptions* options) {
159   DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole,
160                 PostUnmountCallback, options);
161 }
162
163 }  // namespace
164
165 StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) {
166 }
167
168 StorageMonitorMac::~StorageMonitorMac() {
169   if (session_.get()) {
170     DASessionUnscheduleFromRunLoop(
171         session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
172   }
173 }
174
175 void StorageMonitorMac::Init() {
176   session_.reset(DASessionCreate(NULL));
177
178   // Register for callbacks for attached, changed, and removed devices.
179   // This will send notifications for existing devices too.
180   DARegisterDiskAppearedCallback(
181       session_,
182       kDADiskDescriptionMatchVolumeMountable,
183       DiskAppearedCallback,
184       this);
185   DARegisterDiskDisappearedCallback(
186       session_,
187       kDADiskDescriptionMatchVolumeMountable,
188       DiskDisappearedCallback,
189       this);
190   DARegisterDiskDescriptionChangedCallback(
191       session_,
192       kDADiskDescriptionMatchVolumeMountable,
193       kDADiskDescriptionWatchVolumePath,
194       DiskDescriptionChangedCallback,
195       this);
196
197   DASessionScheduleWithRunLoop(
198       session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
199
200   if (base::mac::IsOSLionOrLater()) {
201     image_capture_device_manager_.reset(new ImageCaptureDeviceManager);
202     image_capture_device_manager_->SetNotifications(receiver());
203   }
204 }
205
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));
211
212   pending_disk_updates_--;
213   bool initialization_complete = false;
214   if (!IsInitialized() && pending_disk_updates_ == 0)
215     initialization_complete = true;
216
217   if (info.device_id().empty() || bsd_name.empty()) {
218     if (initialization_complete)
219       MarkInitialized();
220     return;
221   }
222
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());
231     }
232   }
233
234   if (update_type == UPDATE_DEVICE_REMOVED) {
235     if (it != disk_info_map_.end())
236       disk_info_map_.erase(it);
237   } else {
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);
243   }
244
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)
248     MarkInitialized();
249 }
250
251 bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path,
252                                               StorageInfo* device_info) const {
253   DCHECK(device_info);
254
255   if (!path.IsAbsolute())
256     return false;
257
258   base::FilePath current = path;
259   const base::FilePath root(base::FilePath::kSeparators);
260   while (current != root) {
261     StorageInfo info;
262     if (FindDiskWithMountPoint(current, &info)) {
263       *device_info = info;
264       return true;
265     }
266     current = current.DirName();
267   }
268
269   return false;
270 }
271
272 void StorageMonitorMac::EjectDevice(
273       const std::string& device_id,
274       base::Callback<void(EjectStatus)> callback) {
275   StorageInfo::Type type;
276   std::string uuid;
277   if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) {
278     callback.Run(EJECT_FAILURE);
279     return;
280   }
281
282   if (type == StorageInfo::MAC_IMAGE_CAPTURE &&
283       image_capture_device_manager_.get()) {
284     image_capture_device_manager_->EjectDevice(uuid, callback);
285     return;
286   }
287
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);
294       break;
295     }
296   }
297
298   if (bsd_name.empty()) {
299     callback.Run(EJECT_NO_SUCH_DEVICE);
300     return;
301   }
302
303   receiver()->ProcessDetach(device_id);
304
305   base::ScopedCFTypeRef<DADiskRef> disk(
306       DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str()));
307   if (!disk.get()) {
308     callback.Run(StorageMonitor::EJECT_FAILURE);
309     return;
310   }
311   // Get the reference to the full disk for ejecting.
312   disk.reset(DADiskCopyWholeDisk(disk));
313   if (!disk.get()) {
314     callback.Run(StorageMonitor::EJECT_FAILURE);
315     return;
316   }
317
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));
324 }
325
326 // static
327 void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) {
328   StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
329   monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED);
330 }
331
332 // static
333 void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) {
334   StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
335   monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED);
336 }
337
338 // static
339 void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk,
340                                                        CFArrayRef keys,
341                                                        void *context) {
342   StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
343   monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED);
344 }
345
346 void StorageMonitorMac::GetDiskInfoAndUpdate(
347     DADiskRef disk,
348     StorageMonitorMac::UpdateType update_type) {
349   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
350
351   pending_disk_updates_++;
352
353   base::ScopedCFTypeRef<CFDictionaryRef> dict(DADiskCopyDescription(disk));
354   content::BrowserThread::PostTask(
355       content::BrowserThread::FILE,
356       FROM_HERE,
357       base::Bind(GetDiskInfoAndUpdateOnFileThread,
358                  AsWeakPtr(), dict, update_type));
359 }
360
361
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());
370 }
371
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()) {
378       *info = it->second;
379       return true;
380     }
381   }
382   return false;
383 }
384
385 StorageMonitor* StorageMonitor::CreateInternal() {
386   return new StorageMonitorMac();
387 }