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