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_chromeos.h"
7 #include "base/files/file_path.h"
8 #include "base/logging.h"
9 #include "base/stl_util.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "components/storage_monitor/media_storage_util.h"
15 #include "components/storage_monitor/media_transfer_protocol_device_observer_linux.h"
16 #include "components/storage_monitor/removable_device_constants.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "device/media_transfer_protocol/media_transfer_protocol_manager.h"
20 using chromeos::disks::DiskMountManager
;
22 namespace storage_monitor
{
26 // Constructs a device id using uuid or manufacturer (vendor and product) id
28 std::string
MakeDeviceUniqueId(const DiskMountManager::Disk
& disk
) {
29 std::string uuid
= disk
.fs_uuid();
31 return kFSUniqueIdPrefix
+ uuid
;
33 // If one of the vendor or product information is missing, its value in the
35 // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialInfo
36 // TODO(kmadhusu) Extract serial information for the disks and append it to
37 // the device unique id.
38 const std::string
& vendor
= disk
.vendor_id();
39 const std::string
& product
= disk
.product_id();
40 if (vendor
.empty() && product
.empty())
42 return kVendorModelSerialPrefix
+ vendor
+ ":" + product
+ ":";
45 // Returns true if the requested device is valid, else false. On success, fills
47 bool GetDeviceInfo(const DiskMountManager::MountPointInfo
& mount_info
,
51 std::string source_path
= mount_info
.source_path
;
53 const DiskMountManager::Disk
* disk
=
54 DiskMountManager::GetInstance()->FindDiskBySourcePath(source_path
);
55 if (!disk
|| disk
->device_type() == chromeos::DEVICE_TYPE_UNKNOWN
)
58 std::string unique_id
= MakeDeviceUniqueId(*disk
);
59 // Keep track of device uuid and label, to see how often we receive empty
61 base::string16 device_label
= base::UTF8ToUTF16(disk
->device_label());
62 MediaStorageUtil::RecordDeviceInfoHistogram(true, unique_id
, device_label
);
63 if (unique_id
.empty())
66 StorageInfo::Type type
= has_dcim
?
67 StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM
:
68 StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM
;
70 *info
= StorageInfo(StorageInfo::MakeDeviceId(type
, unique_id
),
71 mount_info
.mount_path
,
73 base::UTF8ToUTF16(disk
->vendor_name()),
74 base::UTF8ToUTF16(disk
->product_name()),
75 disk
->total_size_in_bytes());
79 // Returns whether the mount point in |mount_info| is a media device or not.
80 bool CheckMountedPathOnFileThread(
81 const DiskMountManager::MountPointInfo
& mount_info
) {
82 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
83 return MediaStorageUtil::HasDcim(base::FilePath(mount_info
.mount_path
));
88 using content::BrowserThread
;
90 StorageMonitorCros::StorageMonitorCros()
91 : weak_ptr_factory_(this) {
94 StorageMonitorCros::~StorageMonitorCros() {
95 DiskMountManager
* manager
= DiskMountManager::GetInstance();
97 manager
->RemoveObserver(this);
101 void StorageMonitorCros::Init() {
102 DCHECK(DiskMountManager::GetInstance());
103 DiskMountManager::GetInstance()->AddObserver(this);
104 CheckExistingMountPoints();
106 if (!media_transfer_protocol_manager_
) {
107 scoped_refptr
<base::MessageLoopProxy
> loop_proxy
;
108 media_transfer_protocol_manager_
.reset(
109 device::MediaTransferProtocolManager::Initialize(loop_proxy
));
112 media_transfer_protocol_device_observer_
.reset(
113 new MediaTransferProtocolDeviceObserverLinux(
114 receiver(), media_transfer_protocol_manager_
.get()));
117 void StorageMonitorCros::CheckExistingMountPoints() {
118 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
119 const DiskMountManager::MountPointMap
& mount_point_map
=
120 DiskMountManager::GetInstance()->mount_points();
121 for (DiskMountManager::MountPointMap::const_iterator it
=
122 mount_point_map
.begin(); it
!= mount_point_map
.end(); ++it
) {
123 BrowserThread::PostTaskAndReplyWithResult(
124 BrowserThread::FILE, FROM_HERE
,
125 base::Bind(&CheckMountedPathOnFileThread
, it
->second
),
126 base::Bind(&StorageMonitorCros::AddMountedPath
,
127 weak_ptr_factory_
.GetWeakPtr(), it
->second
));
130 // Note: relies on scheduled tasks on the file thread being sequential. This
131 // block needs to follow the for loop, so that the DoNothing call on the FILE
132 // thread happens after the scheduled metadata retrievals, meaning that the
133 // reply callback will then happen after all the AddNewMount calls.
134 BrowserThread::PostTaskAndReply(
135 BrowserThread::FILE, FROM_HERE
,
136 base::Bind(&base::DoNothing
),
137 base::Bind(&StorageMonitorCros::MarkInitialized
,
138 weak_ptr_factory_
.GetWeakPtr()));
141 void StorageMonitorCros::OnDiskEvent(DiskMountManager::DiskEvent event
,
142 const DiskMountManager::Disk
* disk
) {}
144 void StorageMonitorCros::OnDeviceEvent(DiskMountManager::DeviceEvent event
,
145 const std::string
& device_path
) {}
147 void StorageMonitorCros::OnMountEvent(
148 DiskMountManager::MountEvent event
,
149 chromeos::MountError error_code
,
150 const DiskMountManager::MountPointInfo
& mount_info
) {
151 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
153 // Ignore mount points that are not devices.
154 if (mount_info
.mount_type
!= chromeos::MOUNT_TYPE_DEVICE
)
157 if (error_code
!= chromeos::MOUNT_ERROR_NONE
)
159 if (mount_info
.mount_condition
!= chromeos::disks::MOUNT_CONDITION_NONE
)
163 case DiskMountManager::MOUNTING
: {
164 if (ContainsKey(mount_map_
, mount_info
.mount_path
)) {
169 BrowserThread::PostTaskAndReplyWithResult(
170 BrowserThread::FILE, FROM_HERE
,
171 base::Bind(&CheckMountedPathOnFileThread
, mount_info
),
172 base::Bind(&StorageMonitorCros::AddMountedPath
,
173 weak_ptr_factory_
.GetWeakPtr(), mount_info
));
176 case DiskMountManager::UNMOUNTING
: {
177 MountMap::iterator it
= mount_map_
.find(mount_info
.mount_path
);
178 if (it
== mount_map_
.end())
180 receiver()->ProcessDetach(it
->second
.device_id());
181 mount_map_
.erase(it
);
187 void StorageMonitorCros::OnFormatEvent(DiskMountManager::FormatEvent event
,
188 chromeos::FormatError error_code
,
189 const std::string
& device_path
) {}
191 void StorageMonitorCros::SetMediaTransferProtocolManagerForTest(
192 device::MediaTransferProtocolManager
* test_manager
) {
193 DCHECK(!media_transfer_protocol_manager_
);
194 media_transfer_protocol_manager_
.reset(test_manager
);
198 bool StorageMonitorCros::GetStorageInfoForPath(
199 const base::FilePath
& path
,
200 StorageInfo
* device_info
) const {
203 if (media_transfer_protocol_device_observer_
->GetStorageInfoForPath(
204 path
, device_info
)) {
208 if (!path
.IsAbsolute())
211 base::FilePath current
= path
;
212 while (!ContainsKey(mount_map_
, current
.value()) &&
213 current
!= current
.DirName()) {
214 current
= current
.DirName();
217 MountMap::const_iterator info_it
= mount_map_
.find(current
.value());
218 if (info_it
== mount_map_
.end())
221 *device_info
= info_it
->second
;
225 // Callback executed when the unmount call is run by DiskMountManager.
226 // Forwards result to |EjectDevice| caller.
227 void NotifyUnmountResult(
228 base::Callback
<void(StorageMonitor::EjectStatus
)> callback
,
229 chromeos::MountError error_code
) {
230 if (error_code
== chromeos::MOUNT_ERROR_NONE
)
231 callback
.Run(StorageMonitor::EJECT_OK
);
233 callback
.Run(StorageMonitor::EJECT_FAILURE
);
236 void StorageMonitorCros::EjectDevice(
237 const std::string
& device_id
,
238 base::Callback
<void(EjectStatus
)> callback
) {
239 StorageInfo::Type type
;
240 if (!StorageInfo::CrackDeviceId(device_id
, &type
, NULL
)) {
241 callback
.Run(EJECT_FAILURE
);
245 if (type
== StorageInfo::MTP_OR_PTP
) {
246 media_transfer_protocol_device_observer_
->EjectDevice(device_id
, callback
);
250 std::string mount_path
;
251 for (MountMap::const_iterator info_it
= mount_map_
.begin();
252 info_it
!= mount_map_
.end(); ++info_it
) {
253 if (info_it
->second
.device_id() == device_id
)
254 mount_path
= info_it
->first
;
257 if (mount_path
.empty()) {
258 callback
.Run(EJECT_NO_SUCH_DEVICE
);
262 DiskMountManager
* manager
= DiskMountManager::GetInstance();
264 callback
.Run(EJECT_FAILURE
);
268 manager
->UnmountPath(mount_path
, chromeos::UNMOUNT_OPTIONS_NONE
,
269 base::Bind(NotifyUnmountResult
, callback
));
272 device::MediaTransferProtocolManager
*
273 StorageMonitorCros::media_transfer_protocol_manager() {
274 return media_transfer_protocol_manager_
.get();
277 void StorageMonitorCros::AddMountedPath(
278 const DiskMountManager::MountPointInfo
& mount_info
,
280 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
282 if (ContainsKey(mount_map_
, mount_info
.mount_path
)) {
283 // CheckExistingMountPointsOnUIThread() added the mount point information
284 // in the map before the device attached handler is called. Therefore, an
285 // entry for the device already exists in the map.
289 // Get the media device uuid and label if exists.
291 if (!GetDeviceInfo(mount_info
, has_dcim
, &info
))
294 if (info
.device_id().empty())
297 mount_map_
.insert(std::make_pair(mount_info
.mount_path
, info
));
299 receiver()->ProcessAttach(info
);
302 StorageMonitor
* StorageMonitor::CreateInternal() {
303 return new StorageMonitorCros();
306 } // namespace storage_monitor