1 /*
2  * device_enumeration.cpp
3  * Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
4  *
5  * Distributed under terms of the GPL3 license.
6  */
7 
8 #include "global.h"
9 
10 namespace wpd {
11 
12 IPortableDevice*
open_device(const wchar_t * pnp_id,CComPtr<IPortableDeviceValues> & client_information)13 open_device(const wchar_t *pnp_id, CComPtr<IPortableDeviceValues> &client_information) { // {{{
14     CComPtr<IPortableDevice> device;
15     HRESULT hr;
16 
17     Py_BEGIN_ALLOW_THREADS;
18     hr = device.CoCreateInstance(CLSID_PortableDevice, NULL, CLSCTX_INPROC_SERVER);
19     Py_END_ALLOW_THREADS;
20     if (FAILED(hr)) { hresult_set_exc("Failed to create IPortableDevice", hr); device = NULL; }
21     else {
22         Py_BEGIN_ALLOW_THREADS;
23         hr = device->Open(pnp_id, client_information);
24         Py_END_ALLOW_THREADS;
25         if FAILED(hr) {
26             Py_BEGIN_ALLOW_THREADS;
27             device.Release();
28             Py_END_ALLOW_THREADS;
29             hresult_set_exc((hr == E_ACCESSDENIED) ? "Read/write access to device is denied": "Failed to open device", hr);
30         }
31     }
32 
33     return device.Detach();
34 
35 } // }}}
36 
37 static PyObject*
get_storage_info(IPortableDevice * device)38 get_storage_info(IPortableDevice *device) { // {{{
39     HRESULT hr, hr2;
40     CComPtr<IPortableDeviceContent> content = NULL;
41     CComPtr<IEnumPortableDeviceObjectIDs> objects = NULL;
42     CComPtr<IPortableDeviceProperties> properties = NULL;
43     CComPtr<IPortableDeviceKeyCollection> storage_properties = NULL;
44     DWORD fetched, i;
45     GUID guid;
46     ULONGLONG capacity, free_space, capacity_objects, free_objects;
47     ULONG access, storage_type = WPD_STORAGE_TYPE_UNDEFINED;
48 
49     pyobject_raii storage(PyList_New(0));
50 	if (!storage) return NULL;
51 
52     Py_BEGIN_ALLOW_THREADS;
53     hr = device->Content(&content);
54     Py_END_ALLOW_THREADS;
55     if (FAILED(hr)) {hresult_set_exc("Failed to get content interface from device", hr); return NULL;}
56 
57     Py_BEGIN_ALLOW_THREADS;
58     hr = content->Properties(&properties);
59     Py_END_ALLOW_THREADS;
60     if (FAILED(hr)) {hresult_set_exc("Failed to get properties interface", hr); return NULL;}
61 
62     Py_BEGIN_ALLOW_THREADS;
63     hr = storage_properties.CoCreateInstance(CLSID_PortableDeviceKeyCollection, NULL, CLSCTX_INPROC_SERVER);
64     Py_END_ALLOW_THREADS;
65     if (FAILED(hr)) {hresult_set_exc("Failed to create storage properties collection", hr); return NULL;}
66 
67 #define A(what) hr = storage_properties->Add(what); if (FAILED(hr)) { hresult_set_exc("Failed to add storage property " #what " for storage query", hr); return NULL; }
68     A(WPD_OBJECT_CONTENT_TYPE);
69     A(WPD_FUNCTIONAL_OBJECT_CATEGORY);
70     A(WPD_STORAGE_DESCRIPTION);
71     A(WPD_STORAGE_CAPACITY);
72     A(WPD_STORAGE_CAPACITY_IN_OBJECTS);
73     A(WPD_STORAGE_FREE_SPACE_IN_BYTES);
74     A(WPD_STORAGE_FREE_SPACE_IN_OBJECTS);
75     A(WPD_STORAGE_ACCESS_CAPABILITY);
76     A(WPD_STORAGE_FILE_SYSTEM_TYPE);
77     A(WPD_STORAGE_TYPE);
78     A(WPD_OBJECT_NAME);
79 #undef A
80 
81     Py_BEGIN_ALLOW_THREADS;
82     hr = content->EnumObjects(0, WPD_DEVICE_OBJECT_ID, NULL, &objects);
83     Py_END_ALLOW_THREADS;
84     if (FAILED(hr)) {hresult_set_exc("Failed to get objects from device", hr); return NULL;}
85 
86     hr = S_OK;
87     while (hr == S_OK) {
88 		generic_raii_array<wchar_t*, co_task_mem_free, 16> object_ids;
89         Py_BEGIN_ALLOW_THREADS;
90         hr = objects->Next((ULONG)object_ids.size(), object_ids.ptr(), &fetched);
91         Py_END_ALLOW_THREADS;
92         if (SUCCEEDED(hr)) {
93             for(i = 0; i < fetched; i++) {
94 				CComPtr<IPortableDeviceValues> values;
95                 Py_BEGIN_ALLOW_THREADS;
96                 hr2 = properties->GetValues(object_ids[i], storage_properties, &values);
97                 Py_END_ALLOW_THREADS;
98                 if (SUCCEEDED(hr2)) {
99                     if (
100                         SUCCEEDED(values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE, &guid)) && IsEqualGUID(guid, WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT) &&
101                         SUCCEEDED(values->GetGuidValue(WPD_FUNCTIONAL_OBJECT_CATEGORY, &guid)) && IsEqualGUID(guid, WPD_FUNCTIONAL_CATEGORY_STORAGE)
102                        ) {
103                         capacity = 0; capacity_objects = 0; free_space = 0; free_objects = 0;
104                         values->GetUnsignedLargeIntegerValue(WPD_STORAGE_CAPACITY, &capacity);
105                         values->GetUnsignedLargeIntegerValue(WPD_STORAGE_CAPACITY_IN_OBJECTS, &capacity_objects);
106                         values->GetUnsignedLargeIntegerValue(WPD_STORAGE_FREE_SPACE_IN_BYTES, &free_space);
107                         values->GetUnsignedLargeIntegerValue(WPD_STORAGE_FREE_SPACE_IN_OBJECTS, &free_objects);
108                         values->GetUnsignedIntegerValue(WPD_STORAGE_TYPE, &storage_type);
109                         PyObject *paccess = Py_False;
110                         if (SUCCEEDED(values->GetUnsignedIntegerValue(WPD_STORAGE_ACCESS_CAPABILITY, &access)) && access == WPD_STORAGE_ACCESS_CAPABILITY_READWRITE) paccess = Py_True;
111                         pyobject_raii soid(PyUnicode_FromWideChar(object_ids[i], -1));
112                         if (!soid) return NULL;
113                         pyobject_raii so(Py_BuildValue("{s:K, s:K, s:K, s:K, s:O, s:O}",
114                                 "capacity", capacity, "capacity_objects", capacity_objects, "free_space", free_space, "free_objects", free_objects, "rw", paccess, "id", soid.ptr()));
115                         if (!so) return NULL;
116 #define A(which, key) { com_wchar_raii buf; if (SUCCEEDED(values->GetStringValue(which, buf.unsafe_address()))) { \
117 							pyobject_raii d(PyUnicode_FromWideChar(buf.ptr(), -1)); \
118 							if (d) PyDict_SetItemString(so.ptr(), key, d.ptr()); \
119 							else PyErr_Clear(); \
120                         }}
121 						A(WPD_STORAGE_DESCRIPTION, "description");
122 						A(WPD_OBJECT_NAME, "name");
123 						A(WPD_STORAGE_FILE_SYSTEM_TYPE, "filesystem");
124 #undef A
125 						const wchar_t *st;
126                         switch(storage_type) {
127                             case WPD_STORAGE_TYPE_REMOVABLE_RAM:
128                                 st = L"removable_ram";
129                                 break;
130                             case WPD_STORAGE_TYPE_REMOVABLE_ROM:
131                                 st = L"removable_rom";
132                                 break;
133                             case WPD_STORAGE_TYPE_FIXED_RAM:
134                                 st = L"fixed_ram";
135                                 break;
136                             case WPD_STORAGE_TYPE_FIXED_ROM:
137                                 st = L"fixed_rom";
138                                 break;
139                             default:
140                                 st = L"unknown_unknown";
141                         }
142 						pyobject_raii dt(PyUnicode_FromWideChar(st, -1));
143 						if (dt) PyDict_SetItemString(so.ptr(), "type", dt.ptr());
144                         if (PyList_Append(storage.ptr(), so.ptr()) != 0) return NULL;
145                     }
146                 }
147             }
148         }// if(SUCCEEDED(hr))
149     }
150 	return storage.detach();
151 } // }}}
152 
153 PyObject*
get_device_information(CComPtr<IPortableDevice> & device,CComPtr<IPortableDevicePropertiesBulk> & pb)154 get_device_information(CComPtr<IPortableDevice> &device, CComPtr<IPortableDevicePropertiesBulk> &pb) { // {{{
155     CComPtr<IPortableDeviceContent> content = NULL;
156     CComPtr<IPortableDeviceProperties> properties = NULL;
157     CComPtr<IPortableDeviceKeyCollection> keys = NULL;
158     CComPtr<IPortableDeviceValues> values = NULL;
159     CComPtr<IPortableDeviceCapabilities> capabilities = NULL;
160     CComPtr<IPortableDevicePropVariantCollection> categories = NULL;
161     HRESULT hr;
162     DWORD num_of_categories, i;
163     ULONG ti;
164     PyObject *ans = NULL;
165     const char *type = NULL;
166 
167     Py_BEGIN_ALLOW_THREADS;
168     hr = keys.CoCreateInstance(CLSID_PortableDeviceKeyCollection, NULL, CLSCTX_INPROC_SERVER);
169     Py_END_ALLOW_THREADS;
170     if (FAILED(hr)) return hresult_set_exc("Failed to create IPortableDeviceKeyCollection", hr);
171 
172 #define A(what) hr = keys->Add(what); if (FAILED(hr)) { return hresult_set_exc("Failed to add key " #what " to IPortableDeviceKeyCollection", hr); }
173     A(WPD_DEVICE_PROTOCOL);
174     // Despite the MSDN documentation, this does not exist in PortableDevice.h
175     // hr = keys->Add(WPD_DEVICE_TRANSPORT);
176     A(WPD_DEVICE_FRIENDLY_NAME);
177     A(WPD_DEVICE_MANUFACTURER);
178     A(WPD_DEVICE_MODEL);
179     A(WPD_DEVICE_SERIAL_NUMBER);
180     A(WPD_DEVICE_FIRMWARE_VERSION);
181     A(WPD_DEVICE_TYPE);
182 #undef A
183 
184     Py_BEGIN_ALLOW_THREADS;
185     hr = device->Content(&content);
186     Py_END_ALLOW_THREADS;
187     if (FAILED(hr)) return hresult_set_exc("Failed to get IPortableDeviceContent", hr);
188 
189     Py_BEGIN_ALLOW_THREADS;
190     hr = content->Properties(&properties);
191     Py_END_ALLOW_THREADS;
192     if (FAILED(hr)) return hresult_set_exc("Failed to get IPortableDeviceProperties", hr);
193 
194     Py_BEGIN_ALLOW_THREADS;
195     hr = properties->GetValues(WPD_DEVICE_OBJECT_ID, keys, &values);
196     Py_END_ALLOW_THREADS;
197     if(FAILED(hr)) return hresult_set_exc("Failed to get device info", hr);
198 
199     Py_BEGIN_ALLOW_THREADS;
200     hr = device->Capabilities(&capabilities);
201     Py_END_ALLOW_THREADS;
202     if(FAILED(hr)) return hresult_set_exc("Failed to get device capabilities", hr);
203 
204     Py_BEGIN_ALLOW_THREADS;
205     hr = capabilities->GetFunctionalCategories(&categories);
206     Py_END_ALLOW_THREADS;
207     if(FAILED(hr)) return hresult_set_exc("Failed to get device functional categories", hr);
208 
209     Py_BEGIN_ALLOW_THREADS;
210     hr = categories->GetCount(&num_of_categories);
211     Py_END_ALLOW_THREADS;
212     if(FAILED(hr)) return hresult_set_exc("Failed to get device functional categories number", hr);
213 
214     ans = PyDict_New();
215     if (ans == NULL) return NULL;
216 
217 #define S(what, key) { \
218 	com_wchar_raii temp; \
219     if (SUCCEEDED(values->GetStringValue(what, temp.unsafe_address()))) { \
220         pyobject_raii t(PyUnicode_FromWideChar(temp.ptr(), -1)); \
221         if (t) if (PyDict_SetItemString(ans, key, t.ptr()) != 0) PyErr_Clear(); \
222     }}
223 
224 	S(WPD_DEVICE_PROTOCOL, "protocol");
225 
226     if (SUCCEEDED(values->GetUnsignedIntegerValue(WPD_DEVICE_TYPE, &ti))) {
227 		type = "unknown";
228         switch (ti) {
229             case WPD_DEVICE_TYPE_CAMERA:
230                 type = "camera"; break;
231             case WPD_DEVICE_TYPE_MEDIA_PLAYER:
232                 type = "media player"; break;
233             case WPD_DEVICE_TYPE_PHONE:
234                 type = "phone"; break;
235             case WPD_DEVICE_TYPE_VIDEO:
236                 type = "video"; break;
237             case WPD_DEVICE_TYPE_PERSONAL_INFORMATION_MANAGER:
238                 type = "personal information manager"; break;
239             case WPD_DEVICE_TYPE_AUDIO_RECORDER:
240                 type = "audio recorder"; break;
241             case WPD_DEVICE_TYPE_GENERIC:
242                 break;
243         }
244 		pyobject_raii t(PyUnicode_FromString(type));
245 		if (t) PyDict_SetItemString(ans, "type", t.ptr());
246     }
247 
248 	S(WPD_DEVICE_FRIENDLY_NAME, "friendly_name");
249 	S(WPD_DEVICE_MANUFACTURER, "manufacturer_name");
250 	S(WPD_DEVICE_MODEL, "model_name");
251 	S(WPD_DEVICE_SERIAL_NUMBER, "serial_number");
252 	S(WPD_DEVICE_FIRMWARE_VERSION, "device_version");
253 #undef S
254 
255     bool has_storage = false;
256     for (i = 0; i < num_of_categories && !has_storage; i++) {
257         prop_variant pv;
258         if (SUCCEEDED(categories->GetAt(i, &pv)) && pv.puuid != NULL) {
259             if (IsEqualGUID(WPD_FUNCTIONAL_CATEGORY_STORAGE, *pv.puuid)) has_storage = true;
260         }
261     }
262     PyDict_SetItemString(ans, "has_storage", has_storage ? Py_True : Py_False);
263 
264     if (has_storage) {
265         pyobject_raii storage(get_storage_info(device));
266         if (!storage) {
267 			pyobject_raii exc_type, exc_value, exc_tb;
268             PyErr_Fetch(exc_type.unsafe_address(), exc_value.unsafe_address(), exc_tb.unsafe_address());
269             if (exc_type) {
270                 PyErr_NormalizeException(exc_type.unsafe_address(), exc_value.unsafe_address(), exc_tb.unsafe_address());
271                 PyDict_SetItemString(ans, "storage_error", exc_value.ptr());
272             } else {
273 				pyobject_raii t(PyUnicode_FromString("get_storage_info() failed without an error set"));
274                 if (t) PyDict_SetItemString(ans, "storage_error", t.ptr());
275 			}
276         } else {
277 			PyDict_SetItemString(ans, "storage", storage.ptr());
278 		}
279     }
280 
281     Py_BEGIN_ALLOW_THREADS;
282     hr = properties->QueryInterface(IID_PPV_ARGS(&pb));
283     Py_END_ALLOW_THREADS;
284     PyDict_SetItemString(ans, "has_bulk_properties", (FAILED(hr)) ? Py_False: Py_True);
285     return ans;
286 } // }}}
287 
288 } // namespace wpd
289