Blob Blame History Raw
--- plugins/cd/__init__.py	2011-07-18 20:32:50.690850000 -0400
+++ plugins/cd/__init__.py.new	2011-08-31 13:41:50.277924078 -0400
@@ -27,12 +27,12 @@
 
 from xl.nls import gettext as _
 from xl import providers, event
-from xl.hal import Handler
+from xl.hal import Handler, UDisksProvider
 from xl.devices import Device
 import logging
 logger = logging.getLogger(__name__)
 
-PROVIDER = None
+PROVIDER = PROVIDER_UDISKS = None
 
 import dbus, threading, os, struct
 from fcntl import ioctl
@@ -61,15 +61,17 @@
 CDROM_DATA_TRACK = 0x04
 
 def enable(exaile):
-    global PROVIDER
-    PROVIDER = CDHandler()
-    providers.register("hal", PROVIDER)
-
+    global PROVIDER, PROVIDER_UDISKS
+    #~ PROVIDER = CDHandler()
+    #~ providers.register("hal", PROVIDER)
+    PROVIDER_UDISKS = UDisksCdProvider()
+    providers.register("udisks", PROVIDER_UDISKS)
 
 def disable(exaile):
-    global PROVIDER
+    global PROVIDER, PROVIDER_UDISKS
     providers.unregister("hal", PROVIDER)
-    PROVIDER = None
+    providers.unregister("udisks", PROVIDER_UDISKS)
+    PROVIDER = PROVIDER_UDISKS = None
 
 class CDTocParser(object):
     #based on code from http://carey.geek.nz/code/python-cdrom/cdtoc.py
@@ -244,6 +246,24 @@
 
         return cddev
 
+class UDisksCdProvider(UDisksProvider):
+    PRIORITY = UDisksProvider.NORMAL
+    
+    name = 'cd'
+
+    def get_priority(self, obj):
+        props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+        iface = 'org.freedesktop.UDisks.Device'
+        # XXX: We use the number of audio tracks to identify audio CDs.
+        # There may be a better way....
+        compat = props.Get(iface, 'DriveMediaCompatibility')
+        return self.PRIORITY if 'optical_cd' in compat else None
+
+    def create_device(self, obj):
+        props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+        iface = 'org.freedesktop.UDisks.Device'
+        return CDDevice(dev=props.Get(iface, 'DeviceFile'))
+
 
 # vim: et sts=4 sw=4
 
--- xl/main.py	2012-09-09 15:07:41.000000000 -0400
+++ xl/main.py.new	2012-09-24 11:26:49.132168406 -0400
@@ -254,6 +254,8 @@
         # Initialize HAL
         if self.options.Hal:
             from xl import hal
+            self.udisks = hal.UDisks(self.devices)
+            self.udisks.connect()
             self.hal = hal.HAL(self.devices)
             self.hal.connect()
         else:
--- xl/hal.py	2011-07-18 20:32:50.690850000 -0400
+++ xl/hal.py.new	2011-08-31 13:49:02.489520690 -0400
@@ -24,7 +24,7 @@
 # do so. If you do not wish to do so, delete this exception statement
 # from your version.
 
-import logging
+import logging, threading, time
 import dbus
 
 from xl import common, providers, event, devices, settings
@@ -32,6 +32,120 @@
 
 logger = logging.getLogger(__name__)
 
+class UDisks(providers.ProviderHandler):
+    """Provides support for UDisks devices.
+
+    If the D-Bus connection fails, this object will grow a "failed" attribute
+    with True as the value. Plugins should check for this attribute when
+    registering if they want to provide HAL fallback. FIXME: There's a race
+    condition here.
+    """
+
+    # States: start -> init -> addremove <-> listening -> end.
+    # The addremove state acts as a lock against concurrent changes.
+
+    def __init__(self, devicemanager):
+        self._lock = lock = threading.Lock()
+        self._state = 'init'
+
+        providers.ProviderHandler.__init__(self, 'udisks')
+        self.devicemanager = devicemanager
+
+        self.bus = self.obj = self.iface = None
+        self.devices = {}
+        self.providers = {}
+
+    @common.threaded
+    def connect(self):
+        assert self._state == 'init'
+        logger.debug("Connecting to UDisks")
+        try:
+            self.bus = bus = dbus.SystemBus()
+            self.obj = obj = bus.get_object('org.freedesktop.UDisks', '/org/freedesktop/UDisks')
+            self.iface = iface = dbus.Interface(obj, 'org.freedesktop.UDisks')
+            iface.connect_to_signal('DeviceAdded', self._device_added, path_keyword='path')
+            iface.connect_to_signal('DeviceRemoved', self._device_removed, path_keyword='path')
+            logger.info("Connected to UDisks")
+            event.log_event("hal_connected", self, None)
+        except Exception:
+            logger.warning("Failed to connect to UDisks, " \
+                    "autodetection of devices will be disabled.")
+            self._state = 'listening'
+            self.failed = True
+            return
+        self._state = 'addremove'
+        self._add_all()
+        self._state = 'listening'
+
+    def _add_all(self):
+        assert self._state == 'addremove'
+        for path in self.iface.EnumerateDevices():
+            self._add_path(path)
+
+    def _add_path(self, path):
+        assert self._state == 'addremove'
+        obj = self.bus.get_object('org.freedesktop.UDisks', path)
+        old, new = self._get_provider_for(obj)
+        if new is not old:
+            if old[0]:
+                self.devicemanager.remove_device(self.devices[path])
+            device = new[0].create_device(obj)
+            device.autoconnect()
+            self.devicemanager.add_device(device)
+            self.providers[path] = new
+            self.devices[path] = device
+
+    def _get_provider_for(self, obj):
+        """Return (old_provider, old_priority), (new_provider, new_priority)"""
+        assert self._state == 'addremove'
+        path = obj.object_path
+        highest = old = self.providers.get(path, (None, -1))
+        for provider in self.get_providers():
+            priority = provider.get_priority(obj)
+            if priority is not None and priority > highest[1]:
+                highest = (provider, priority)
+        return old, highest
+
+    def _remove_path(self, path):
+        assert self._state == 'addremove'
+        self.devicemanager.remove_device(self.devices[path])
+        del self.devices[path]
+
+    def _device_added(self, path):
+        import pdb; pdb.set_trace()
+        self._addremove()
+        self._add_path(path)
+        self._state = 'listening'
+
+    def _device_removed(self, path):
+        self._addremove()
+        try:
+            self._remove_path(path)
+        except KeyError: # Not ours
+            pass
+        self._state = 'listening'
+
+    def on_provider_added(self, provider):
+        self._addremove()
+        self._connect_all()
+        self._state = 'listening'
+
+    def on_provider_removed(self, provider):
+        self._addremove()
+        for path, provider_ in self.providers.iteritems():
+            if provider_ is provider:
+                self._remove_path(path)
+        self._state = 'listening'
+
+    def _addremove(self):
+        """Helper to transition safely to the addremove state"""
+        while True:
+            with self._lock:
+                if self._state == 'listening':
+                    self._state = 'addremove'
+                    break
+            time.sleep(1)
+
 class HAL(providers.ProviderHandler):
     """
         HAL interface
@@ -144,6 +258,13 @@
     def device_from_udi(self, hal, udi):
         pass
 
+class UDisksProvider:
+    VERY_LOW, LOW, NORMAL, HIGH, VERY_HIGH = range(0, 101, 25)
+    def get_priority(self, obj):
+        pass  # return: int [0..100] or None
+    def get_device(self, obj):
+        pass  # return: xl.devices.Device
+
 
 # vim: et sts=4 sw=4