Merge remote branch 'origin/trunk' into trac-8658
[xbmc:xbmc-antiquated.git] / xbmc / linux / UDisksProvider.cpp
1 /*
2  *      Copyright (C) 2005-2009 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21 #include "UDisksProvider.h"
22 #ifdef HAS_DBUS
23 #include "Util.h"
24 #include "AdvancedSettings.h"
25 #include "LocalizeStrings.h"
26 #include "log.h"
27
28 CUDiskDevice::CUDiskDevice(const char *DeviceKitUDI)
29 {
30   m_DeviceKitUDI = DeviceKitUDI;
31   m_UDI = "";
32   m_MountPath = "";
33   m_FileSystem = "";
34   m_isMounted = false;
35   m_isMountedByUs = false;
36   m_isRemovable = false;
37   m_isPartition = false;
38   m_isFileSystem = false;
39   m_isSystemInternal = false;
40   m_isOptical = false;
41   m_PartitionSizeGiB = 0.0f;
42   Update();
43 }
44
45 void CUDiskDevice::Update()
46 {
47   CVariant properties = CDBusUtil::GetAll("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device");
48
49   m_isFileSystem = CStdString(properties["IdUsage"].asString()) == "filesystem";
50   if (m_isFileSystem)
51   {
52     m_UDI         = properties["IdUuid"].asString();
53     m_Label       = properties["IdLabel"].asString();
54     m_FileSystem  = properties["IdType"].asString();
55   }
56   else
57   {
58     m_UDI.clear();
59     m_Label.clear();
60     m_FileSystem.clear();
61   }
62
63   m_isMounted   = properties["DeviceIsMounted"].asBoolean();
64   if (m_isMounted && properties["DeviceMountPaths"].size() > 0)
65     m_MountPath   = properties["DeviceMountPaths"][0].asString();
66   else
67     m_MountPath.clear();
68
69   m_PartitionSizeGiB = properties["PartitionSize"].asUnsignedInteger() / 1024.0 / 1024.0 / 1024.0;
70   m_isPartition = properties["DeviceIsPartition"].asBoolean();
71   m_isSystemInternal = properties["DeviceIsSystemInternal"].asBoolean();
72   m_isOptical = properties["DeviceIsOpticalDisc"].asBoolean();
73   if (m_isPartition)
74   {
75     CVariant isRemovable = CDBusUtil::GetVariant("org.freedesktop.UDisks", properties["PartitionSlave"].asString(), "org.freedesktop.UDisks.Device", "DeviceIsRemovable");
76
77     if ( !isRemovable.isNull() )
78       m_isRemovable = isRemovable.asBoolean();
79     else
80       m_isRemovable = false;
81   }
82   else
83     m_isRemovable = properties["DeviceIsRemovable"].asBoolean();
84 }
85
86 bool CUDiskDevice::Mount()
87 {
88   if (!m_isMounted && !m_isSystemInternal && m_isFileSystem)
89   {
90     CLog::Log(LOGDEBUG, "UDisks: Mounting %s", m_DeviceKitUDI.c_str());
91     CDBusMessage message("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device", "FilesystemMount");
92     message.AppendArgument("");
93     const char *array[] = {};
94     message.AppendArgument(array, 0);
95
96     DBusMessage *reply = message.SendSystem();
97     if (reply)
98     {
99       char *mountPoint;
100       if (dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &mountPoint, DBUS_TYPE_INVALID))
101       {
102         m_MountPath = mountPoint;
103         CLog::Log(LOGDEBUG, "UDisks: Sucessfully mounted %s on %s", m_DeviceKitUDI.c_str(), mountPoint);
104         m_isMountedByUs = m_isMounted = true;
105       }
106     }
107
108     return m_isMounted;
109   }
110   else
111     CLog::Log(LOGDEBUG, "UDisks: Is not able to mount %s", toString().c_str());
112
113   return false;
114 }
115
116 bool CUDiskDevice::UnMount()
117 {
118   if (m_isMounted && !m_isSystemInternal && m_isFileSystem)
119   {
120     CDBusMessage message("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device", "FilesystemUnmount");
121
122     const char *array[1];
123     message.AppendArgument(array, 0);
124
125     DBusMessage *reply = message.SendSystem();
126     if (reply)
127       m_isMountedByUs = m_isMounted = false;
128
129     return !m_isMounted;
130   }
131   else
132     CLog::Log(LOGDEBUG, "UDisks: Is not able to unmount %s", toString().c_str());
133
134   return false;
135 }
136
137 CMediaSource CUDiskDevice::ToMediaShare()
138 {
139   CMediaSource source;
140   source.strPath = m_MountPath;
141   if (m_Label.empty())
142     source.strName.Format("%.1f GB %s", m_PartitionSizeGiB, g_localizeStrings.Get(155).c_str());
143   else
144     source.strName = m_Label;
145   if (m_isOptical)
146     source.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD;
147   else if (m_isSystemInternal)
148     source.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL;
149   else
150     source.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE;
151   source.m_ignore = true;
152   return source;
153 }
154
155 bool CUDiskDevice::IsApproved()
156 {
157   return (m_isFileSystem && m_isMounted && m_UDI.length() > 0 && (m_FileSystem.length() > 0 && !m_FileSystem.Equals("swap")) && !m_MountPath.Equals("/")) || m_isOptical;
158 }
159
160 #define BOOL2SZ(b) ((b) ? "true" : "false")
161
162 CStdString CUDiskDevice::toString()
163 {
164   CStdString str;
165   str.Format("DeviceUDI %s: IsFileSystem %s HasFileSystem %s "
166       "IsSystemInternal %s IsMounted %s IsRemovable %s IsPartition %s "
167       "IsOptical %s",
168       m_DeviceKitUDI.c_str(), BOOL2SZ(m_isFileSystem), m_FileSystem,
169       BOOL2SZ(m_isSystemInternal), BOOL2SZ(m_isMounted),
170       BOOL2SZ(m_isRemovable), BOOL2SZ(m_isPartition), BOOL2SZ(m_isOptical));
171
172   return str;
173 }
174
175 CUDisksProvider::CUDisksProvider()
176 {
177   dbus_error_init (&m_error);
178   // TODO: do not use dbus_connection_pop_message() that requires the use of a
179   // private connection
180   m_connection = dbus_bus_get_private(DBUS_BUS_SYSTEM, &m_error);
181   dbus_connection_set_exit_on_disconnect(m_connection, false);
182
183   dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.UDisks'", &m_error);
184   dbus_connection_flush(m_connection);
185   if (dbus_error_is_set(&m_error))
186   {
187     CLog::Log(LOGERROR, "UDisks: Failed to attach to signal %s", m_error.message);
188     dbus_connection_close(m_connection);
189     dbus_connection_unref(m_connection);
190     m_connection = NULL;
191   }
192 }
193
194 CUDisksProvider::~CUDisksProvider()
195 {
196   DeviceMap::iterator itr;
197
198   for (itr = m_AvailableDevices.begin(); itr != m_AvailableDevices.end(); ++itr)
199     delete m_AvailableDevices[itr->first];
200
201   m_AvailableDevices.clear();
202
203   if (m_connection)
204   {
205     dbus_connection_close(m_connection);
206     dbus_connection_unref(m_connection);
207     m_connection = NULL;
208   }
209
210   dbus_error_free (&m_error);
211 }
212
213 void CUDisksProvider::Initialize()
214 {
215   CLog::Log(LOGDEBUG, "Selected UDisks as storage provider");
216   m_DaemonVersion = atoi(CDBusUtil::GetVariant("org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "DaemonVersion").asString());
217   CLog::Log(LOGDEBUG, "UDisks: DaemonVersion %i", m_DaemonVersion);
218
219   CLog::Log(LOGDEBUG, "UDisks: Querying available devices");
220   std::vector<CStdString> devices = EnumerateDisks();
221   for (unsigned int i = 0; i < devices.size(); i++)
222     DeviceAdded(devices[i].c_str(), NULL);
223 }
224
225 bool CUDisksProvider::Eject(CStdString mountpath)
226 {
227   DeviceMap::iterator itr;
228   CStdString path(mountpath);
229   CUtil::RemoveSlashAtEnd(path);
230
231   for (itr = m_AvailableDevices.begin(); itr != m_AvailableDevices.end(); ++itr)
232   {
233     CUDiskDevice *device = itr->second;
234     if (device->m_MountPath.Equals(path))
235       return device->UnMount();
236   }
237
238   return false;
239 }
240
241 std::vector<CStdString> CUDisksProvider::GetDiskUsage()
242 {
243   std::vector<CStdString> devices;
244   DeviceMap::iterator itr;
245
246   for(itr = m_AvailableDevices.begin(); itr != m_AvailableDevices.end(); ++itr)
247   {
248     CUDiskDevice *device = itr->second;
249     if (device->m_isMounted)
250     {
251       CStdString str;
252       str.Format("%s %.1f GiB", device->m_MountPath.c_str(), device->m_PartitionSizeGiB);
253       devices.push_back(str);
254     }
255   }
256
257   return devices;
258 }
259
260 bool CUDisksProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback)
261 {
262   bool result = false;
263   if (m_connection)
264   {
265     dbus_connection_read_write(m_connection, 0);
266     DBusMessage *msg = dbus_connection_pop_message(m_connection);
267
268     if (msg)
269     {
270       char *object;
271       if (dbus_message_get_args (msg, NULL, DBUS_TYPE_OBJECT_PATH, &object, DBUS_TYPE_INVALID))
272       {
273         result = true;
274         if (dbus_message_is_signal(msg, "org.freedesktop.UDisks", "DeviceAdded"))
275           DeviceAdded(object, callback);
276         else if (dbus_message_is_signal(msg, "org.freedesktop.UDisks", "DeviceRemoved"))
277           DeviceRemoved(object, callback);
278         else if (dbus_message_is_signal(msg, "org.freedesktop.UDisks", "DeviceChanged"))
279           DeviceChanged(object, callback);
280       }
281       dbus_message_unref(msg);
282     }
283   }
284   return result;
285 }
286
287 bool CUDisksProvider::HasUDisks()
288 {
289   bool hasUDisks = false;
290   CDBusMessage message("org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "EnumerateDevices");
291
292   DBusError error;
293   dbus_error_init (&error);
294   DBusConnection *con = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
295   if (!con)
296     return false;
297
298   message.Send(con, &error);
299
300   if (!dbus_error_is_set(&error))
301     hasUDisks = true;
302   else
303     CLog::Log(LOGDEBUG, "UDisks: %s - %s", error.name, error.message);
304
305   dbus_error_free (&error);
306   dbus_connection_unref(con);
307
308   return hasUDisks;
309 }
310
311 void CUDisksProvider::DeviceAdded(const char *object, IStorageEventsCallback *callback)
312 {
313   CLog::Log(LOGDEBUG, "UDisks: DeviceAdded (%s)", object);
314
315   if (m_AvailableDevices[object])
316   {
317     CLog::Log(LOGWARNING, "UDisks: Inconsistency found! DeviceAdded on an indexed disk");
318     delete m_AvailableDevices[object];
319   }
320
321   CUDiskDevice *device = NULL;
322     device = new CUDiskDevice(object);
323   m_AvailableDevices[object] = device;
324
325   if (g_advancedSettings.m_handleMounting)
326     device->Mount();
327
328   CLog::Log(LOGDEBUG, "UDisks: DeviceAdded - %s", device->toString().c_str());
329   if (device->m_isMounted && device->IsApproved())
330   {
331     CLog::Log(LOGNOTICE, "UDisks: Added %s", device->m_MountPath.c_str());
332     if (callback)
333       callback->OnStorageAdded(device->m_Label, device->m_MountPath);
334   }
335 }
336
337 void CUDisksProvider::DeviceRemoved(const char *object, IStorageEventsCallback *callback)
338 {
339   CLog::Log(LOGDEBUG, "UDisks: DeviceRemoved (%s)", object);
340
341   CUDiskDevice *device = m_AvailableDevices[object];
342   if (device)
343   {
344     if (device->m_isMounted && callback)
345       callback->OnStorageUnsafelyRemoved(device->m_Label);
346
347     delete m_AvailableDevices[object];
348     m_AvailableDevices.erase(object);
349   }
350 }
351
352 void CUDisksProvider::DeviceChanged(const char *object, IStorageEventsCallback *callback)
353 {
354   CLog::Log(LOGDEBUG, "UDisks: DeviceChanged (%s)", object);
355
356   CUDiskDevice *device = m_AvailableDevices[object];
357   if (device == NULL)
358   {
359     CLog::Log(LOGWARNING, "UDisks: Inconsistency found! DeviceChanged on an unindexed disk");
360     DeviceAdded(object, callback);
361   }
362   else
363   {
364     bool mounted = device->m_isMounted;
365     device->Update();
366     if (!mounted && device->m_isMounted && callback)
367       callback->OnStorageAdded(device->m_MountPath, device->m_Label);
368     else if (mounted && !device->m_isMounted && callback)
369       callback->OnStorageSafelyRemoved(device->m_Label);
370
371     CLog::Log(LOGDEBUG, "UDisks: DeviceChanged - %s", device->toString().c_str());
372   }
373 }
374
375 std::vector<CStdString> CUDisksProvider::EnumerateDisks()
376 {
377   std::vector<CStdString> devices;
378   CDBusMessage message("org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "EnumerateDevices");
379   DBusMessage *reply = message.SendSystem();
380   if (reply)
381   {
382     char** disks  = NULL;
383     int    length = 0;
384
385     if (dbus_message_get_args (reply, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &disks, &length, DBUS_TYPE_INVALID))
386     {
387       for (int i = 0; i < length; i++)
388         devices.push_back(disks[i]);
389
390       dbus_free_string_array(disks);
391     }
392   }
393
394   return devices;
395 }
396
397 void CUDisksProvider::GetDisks(VECSOURCES& devices, bool EnumerateRemovable)
398 {
399   DeviceMap::iterator itr;
400
401   for (itr = m_AvailableDevices.begin(); itr != m_AvailableDevices.end(); ++itr)
402   {
403     CUDiskDevice *device = itr->second;
404     if (device && device->IsApproved() && device->m_isSystemInternal != EnumerateRemovable)
405       devices.push_back(device->ToMediaShare());
406   }
407 }
408 #endif