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_win.h"
6
7#include <windows.h>
8#include <dbt.h>
9#include <fileapi.h>
10#include <shlobj.h>
11
12#include "base/win/wrapped_window_proc.h"
13#include "components/storage_monitor/portable_device_watcher_win.h"
14#include "components/storage_monitor/removable_device_constants.h"
15#include "components/storage_monitor/storage_info.h"
16#include "components/storage_monitor/volume_mount_watcher_win.h"
17
18#define WM_USER_MEDIACHANGED (WM_USER + 5)
19
20// StorageMonitorWin -------------------------------------------------------
21
22namespace storage_monitor {
23
24StorageMonitorWin::StorageMonitorWin(
25    VolumeMountWatcherWin* volume_mount_watcher,
26    PortableDeviceWatcherWin* portable_device_watcher)
27    : window_class_(0),
28      instance_(NULL),
29      window_(NULL),
30      shell_change_notify_id_(0),
31      volume_mount_watcher_(volume_mount_watcher),
32      portable_device_watcher_(portable_device_watcher) {
33  DCHECK(volume_mount_watcher_);
34  DCHECK(portable_device_watcher_);
35  volume_mount_watcher_->SetNotifications(receiver());
36  portable_device_watcher_->SetNotifications(receiver());
37}
38
39StorageMonitorWin::~StorageMonitorWin() {
40  if (shell_change_notify_id_)
41    SHChangeNotifyDeregister(shell_change_notify_id_);
42  volume_mount_watcher_->SetNotifications(NULL);
43  portable_device_watcher_->SetNotifications(NULL);
44
45  if (window_)
46    DestroyWindow(window_);
47
48  if (window_class_)
49    UnregisterClass(MAKEINTATOM(window_class_), instance_);
50}
51
52void StorageMonitorWin::Init() {
53  WNDCLASSEX window_class;
54  base::win::InitializeWindowClass(
55      L"Chrome_StorageMonitorWindow",
56      &base::win::WrappedWindowProc<StorageMonitorWin::WndProcThunk>,
57      0, 0, 0, NULL, NULL, NULL, NULL, NULL,
58      &window_class);
59  instance_ = window_class.hInstance;
60  window_class_ = RegisterClassEx(&window_class);
61  DCHECK(window_class_);
62
63  window_ = CreateWindow(MAKEINTATOM(window_class_), 0, 0, 0, 0, 0, 0, 0, 0,
64                         instance_, 0);
65  SetWindowLongPtr(window_, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
66  volume_mount_watcher_->Init();
67  portable_device_watcher_->Init(window_);
68  MediaChangeNotificationRegister();
69}
70
71bool StorageMonitorWin::GetStorageInfoForPath(const base::FilePath& path,
72                                              StorageInfo* device_info) const {
73  DCHECK(device_info);
74
75  // TODO(gbillock): Move this logic up to StorageMonitor.
76  // If we already know the StorageInfo for the path, just return it.
77  // This will account for portable devices as well.
78  std::vector<StorageInfo> attached_devices = GetAllAvailableStorages();
79  size_t best_parent = attached_devices.size();
80  size_t best_length = 0;
81  for (size_t i = 0; i < attached_devices.size(); i++) {
82    if (!StorageInfo::IsRemovableDevice(attached_devices[i].device_id()))
83      continue;
84    base::FilePath relative;
85    if (base::FilePath(attached_devices[i].location()).AppendRelativePath(
86            path, &relative)) {
87      // Note: the relative path is longer for shorter shared path between
88      // the path and the device mount point, so we want the shortest
89      // relative path.
90      if (relative.value().size() < best_length) {
91        best_parent = i;
92        best_length = relative.value().size();
93      }
94    }
95  }
96  if (best_parent != attached_devices.size()) {
97    *device_info = attached_devices[best_parent];
98    return true;
99  }
100
101  return GetDeviceInfo(path, device_info);
102}
103
104void StorageMonitorWin::EjectDevice(
105    const std::string& device_id,
106    base::Callback<void(EjectStatus)> callback) {
107  StorageInfo::Type type;
108  if (!StorageInfo::CrackDeviceId(device_id, &type, NULL)) {
109    callback.Run(EJECT_FAILURE);
110    return;
111  }
112
113  if (type == StorageInfo::MTP_OR_PTP)
114    portable_device_watcher_->EjectDevice(device_id, callback);
115  else if (StorageInfo::IsRemovableDevice(device_id))
116    volume_mount_watcher_->EjectDevice(device_id, callback);
117  else
118    callback.Run(EJECT_FAILURE);
119}
120
121bool StorageMonitorWin::GetMTPStorageInfoFromDeviceId(
122    const std::string& storage_device_id,
123    base::string16* device_location,
124    base::string16* storage_object_id) const {
125  StorageInfo::Type type;
126  StorageInfo::CrackDeviceId(storage_device_id, &type, NULL);
127  return ((type == StorageInfo::MTP_OR_PTP) &&
128      portable_device_watcher_->GetMTPStorageInfoFromDeviceId(
129          storage_device_id, device_location, storage_object_id));
130}
131
132// static
133LRESULT CALLBACK StorageMonitorWin::WndProcThunk(HWND hwnd, UINT message,
134                                                 WPARAM wparam, LPARAM lparam) {
135  StorageMonitorWin* msg_wnd = reinterpret_cast<StorageMonitorWin*>(
136      GetWindowLongPtr(hwnd, GWLP_USERDATA));
137  if (msg_wnd)
138    return msg_wnd->WndProc(hwnd, message, wparam, lparam);
139  return ::DefWindowProc(hwnd, message, wparam, lparam);
140}
141
142LRESULT CALLBACK StorageMonitorWin::WndProc(HWND hwnd, UINT message,
143                                            WPARAM wparam, LPARAM lparam) {
144  switch (message) {
145    case WM_DEVICECHANGE:
146      OnDeviceChange(static_cast<UINT>(wparam), lparam);
147      return TRUE;
148    case WM_USER_MEDIACHANGED:
149      OnMediaChange(wparam, lparam);
150      return TRUE;
151    default:
152      break;
153  }
154
155  return ::DefWindowProc(hwnd, message, wparam, lparam);
156}
157
158void StorageMonitorWin::MediaChangeNotificationRegister() {
159  LPITEMIDLIST id_list;
160  if (SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &id_list) == NOERROR) {
161    SHChangeNotifyEntry notify_entry;
162    notify_entry.pidl = id_list;
163    notify_entry.fRecursive = TRUE;
164    shell_change_notify_id_ = SHChangeNotifyRegister(
165        window_, SHCNRF_ShellLevel, SHCNE_MEDIAINSERTED | SHCNE_MEDIAREMOVED,
166        WM_USER_MEDIACHANGED, 1, &notify_entry);
167    if (!shell_change_notify_id_)
168      DVLOG(1) << "SHChangeNotifyRegister FAILED";
169  } else {
170    DVLOG(1) << "SHGetSpecialFolderLocation FAILED";
171  }
172}
173
174bool StorageMonitorWin::GetDeviceInfo(const base::FilePath& device_path,
175                                      StorageInfo* info) const {
176  DCHECK(info);
177
178  // TODO(kmadhusu) Implement PortableDeviceWatcherWin::GetDeviceInfo()
179  // function when we have the functionality to add a sub directory of
180  // portable device as a media gallery.
181  return volume_mount_watcher_->GetDeviceInfo(device_path, info);
182}
183
184void StorageMonitorWin::OnDeviceChange(UINT event_type, LPARAM data) {
185  DVLOG(1) << "OnDeviceChange " << event_type << " " << data;
186  volume_mount_watcher_->OnWindowMessage(event_type, data);
187  portable_device_watcher_->OnWindowMessage(event_type, data);
188}
189
190void StorageMonitorWin::OnMediaChange(WPARAM wparam, LPARAM lparam) {
191  volume_mount_watcher_->OnMediaChange(wparam, lparam);
192}
193
194StorageMonitor* StorageMonitor::CreateInternal() {
195  return new StorageMonitorWin(new VolumeMountWatcherWin(),
196                               new PortableDeviceWatcherWin());
197}
198
199}  // namespace storage_monitor
200