media_transfer_protocol_device_observer_linux.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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/media_transfer_protocol_device_observer_linux.h"
6
7#include <vector>
8
9#include "base/files/file_path.h"
10#include "base/stl_util.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/string_split.h"
13#include "base/strings/utf_string_conversions.h"
14#include "components/storage_monitor/media_storage_util.h"
15#include "components/storage_monitor/removable_device_constants.h"
16#include "device/media_transfer_protocol/mtp_storage_info.pb.h"
17
18namespace {
19
20// Device root path constant.
21const char kRootPath[] = "/";
22
23// Constructs and returns the location of the device using the |storage_name|.
24std::string GetDeviceLocationFromStorageName(const std::string& storage_name) {
25  // Construct a dummy device path using the storage name. This is only used
26  // for registering device media file system.
27  // E.g.: If the |storage_name| is "usb:2,2:12345" then "/usb:2,2:12345" is the
28  // device location.
29  DCHECK(!storage_name.empty());
30  return kRootPath + storage_name;
31}
32
33// Returns the storage identifier of the device from the given |storage_name|.
34// E.g. If the |storage_name| is "usb:2,2:65537", the storage identifier is
35// "65537".
36std::string GetStorageIdFromStorageName(const std::string& storage_name) {
37  std::vector<std::string> name_parts;
38  base::SplitString(storage_name, ':', &name_parts);
39  return name_parts.size() == 3 ? name_parts[2] : std::string();
40}
41
42// Returns a unique device id from the given |storage_info|.
43std::string GetDeviceIdFromStorageInfo(const MtpStorageInfo& storage_info) {
44  const std::string storage_id =
45      GetStorageIdFromStorageName(storage_info.storage_name());
46  if (storage_id.empty())
47    return std::string();
48
49  // Some devices have multiple data stores. Therefore, include storage id as
50  // part of unique id along with vendor, model and volume information.
51  const std::string vendor_id = base::UintToString(storage_info.vendor_id());
52  const std::string model_id = base::UintToString(storage_info.product_id());
53  return StorageInfo::MakeDeviceId(
54      StorageInfo::MTP_OR_PTP,
55      kVendorModelVolumeStoragePrefix + vendor_id + ":" + model_id + ":" +
56          storage_info.volume_identifier() + ":" + storage_id);
57}
58
59// Returns the |data_store_id| string in the required format.
60// If the |data_store_id| is 65537, this function returns " (65537)".
61std::string GetFormattedIdString(const std::string& data_store_id) {
62  return ("(" + data_store_id + ")");
63}
64
65// Helper function to get device label from storage information.
66base::string16 GetDeviceLabelFromStorageInfo(
67    const MtpStorageInfo& storage_info) {
68  std::string device_label;
69  const std::string& vendor_name = storage_info.vendor();
70  device_label = vendor_name;
71
72  const std::string& product_name = storage_info.product();
73  if (!product_name.empty()) {
74    if (!device_label.empty())
75      device_label += " ";
76    device_label += product_name;
77  }
78
79  // Add the data store id to the device label.
80  if (!device_label.empty()) {
81    const std::string& volume_id = storage_info.volume_identifier();
82    if (!volume_id.empty()) {
83      device_label += GetFormattedIdString(volume_id);
84    } else {
85      const std::string data_store_id =
86          GetStorageIdFromStorageName(storage_info.storage_name());
87      if (!data_store_id.empty())
88        device_label += GetFormattedIdString(data_store_id);
89    }
90  }
91  return base::UTF8ToUTF16(device_label);
92}
93
94// Helper function to get the device storage details such as device id, label
95// and location. On success and fills in |id|, |label| and |location|.
96void GetStorageInfo(const std::string& storage_name,
97                    device::MediaTransferProtocolManager* mtp_manager,
98                    std::string* id,
99                    base::string16* label,
100                    std::string* location) {
101  DCHECK(!storage_name.empty());
102  const MtpStorageInfo* storage_info =
103      mtp_manager->GetStorageInfo(storage_name);
104
105  if (!storage_info)
106    return;
107
108  *id = GetDeviceIdFromStorageInfo(*storage_info);
109  *label = GetDeviceLabelFromStorageInfo(*storage_info);
110  *location = GetDeviceLocationFromStorageName(storage_name);
111}
112
113}  // namespace
114
115MediaTransferProtocolDeviceObserverLinux::
116MediaTransferProtocolDeviceObserverLinux(
117    StorageMonitor::Receiver* receiver,
118    device::MediaTransferProtocolManager* mtp_manager)
119    : mtp_manager_(mtp_manager),
120      get_storage_info_func_(&GetStorageInfo),
121      notifications_(receiver) {
122  mtp_manager_->AddObserver(this);
123  EnumerateStorages();
124}
125
126// This constructor is only used by unit tests.
127MediaTransferProtocolDeviceObserverLinux::
128MediaTransferProtocolDeviceObserverLinux(
129    StorageMonitor::Receiver* receiver,
130    device::MediaTransferProtocolManager* mtp_manager,
131    GetStorageInfoFunc get_storage_info_func)
132    : mtp_manager_(mtp_manager),
133      get_storage_info_func_(get_storage_info_func),
134      notifications_(receiver) {
135}
136
137MediaTransferProtocolDeviceObserverLinux::
138~MediaTransferProtocolDeviceObserverLinux() {
139  mtp_manager_->RemoveObserver(this);
140}
141
142bool MediaTransferProtocolDeviceObserverLinux::GetStorageInfoForPath(
143    const base::FilePath& path,
144    StorageInfo* storage_info) const {
145  DCHECK(storage_info);
146
147  if (!path.IsAbsolute())
148    return false;
149
150  std::vector<base::FilePath::StringType> path_components;
151  path.GetComponents(&path_components);
152  if (path_components.size() < 2)
153    return false;
154
155  // First and second component of the path specifies the device location.
156  // E.g.: If |path| is "/usb:2,2:65537/DCIM/Folder_a", "/usb:2,2:65537" is the
157  // device location.
158  StorageLocationToInfoMap::const_iterator info_it =
159      storage_map_.find(GetDeviceLocationFromStorageName(path_components[1]));
160  if (info_it == storage_map_.end())
161    return false;
162
163  *storage_info = info_it->second;
164  return true;
165}
166
167void MediaTransferProtocolDeviceObserverLinux::EjectDevice(
168    const std::string& device_id,
169    base::Callback<void(StorageMonitor::EjectStatus)> callback) {
170  std::string location;
171  if (!GetLocationForDeviceId(device_id, &location)) {
172    callback.Run(StorageMonitor::EJECT_NO_SUCH_DEVICE);
173    return;
174  }
175
176  // TODO(thestig): Change this to tell the mtp manager to eject the device.
177
178  StorageChanged(false, location);
179  callback.Run(StorageMonitor::EJECT_OK);
180}
181
182// device::MediaTransferProtocolManager::Observer override.
183void MediaTransferProtocolDeviceObserverLinux::StorageChanged(
184    bool is_attached,
185    const std::string& storage_name) {
186  DCHECK(!storage_name.empty());
187
188  // New storage is attached.
189  if (is_attached) {
190    std::string device_id;
191    base::string16 device_name;
192    std::string location;
193    get_storage_info_func_(storage_name, mtp_manager_,
194                           &device_id, &device_name, &location);
195
196    // Keep track of device id and device name to see how often we receive
197    // empty values.
198    MediaStorageUtil::RecordDeviceInfoHistogram(false, device_id, device_name);
199    if (device_id.empty() || device_name.empty())
200      return;
201
202    DCHECK(!ContainsKey(storage_map_, location));
203
204    StorageInfo storage_info(device_id, device_name, location, device_name,
205                             base::string16(), base::string16(), 0);
206    storage_map_[location] = storage_info;
207    notifications_->ProcessAttach(storage_info);
208  } else {
209    // Existing storage is detached.
210    StorageLocationToInfoMap::iterator it =
211        storage_map_.find(GetDeviceLocationFromStorageName(storage_name));
212    if (it == storage_map_.end())
213      return;
214    notifications_->ProcessDetach(it->second.device_id());
215    storage_map_.erase(it);
216  }
217}
218
219void MediaTransferProtocolDeviceObserverLinux::EnumerateStorages() {
220  typedef std::vector<std::string> StorageList;
221  StorageList storages = mtp_manager_->GetStorages();
222  for (StorageList::const_iterator storage_iter = storages.begin();
223       storage_iter != storages.end(); ++storage_iter) {
224    StorageChanged(true, *storage_iter);
225  }
226}
227
228bool MediaTransferProtocolDeviceObserverLinux::GetLocationForDeviceId(
229    const std::string& device_id, std::string* location) const {
230  for (StorageLocationToInfoMap::const_iterator it = storage_map_.begin();
231       it != storage_map_.end(); ++it) {
232    if (it->second.device_id() == device_id) {
233      *location = it->first;
234      return true;
235    }
236  }
237
238  return false;
239}
240