# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Authors: Philippe Normand <philippe@fluendo.com>
# ported to elisa 0.5 by: Benjamin Kampmann <benjamin@fluendo.com>
# major refactoring by Guillaume Emont <guillaume@fluendo.com>

"""
HALResourceProvider
"""

import dbus
if dbus.version < (0, 82, 0):
    raise ImportError, "This plugin needs dbus > 0.82"

from elisa.core.components.resource_provider import ResourceProvider
from elisa.core import common
from elisa.core.utils.defer import DeferredList

from elisa.plugins.base.messages.device import NewDeviceDetected, \
                                               NewUnknownDevice, \
                                               DeviceRemoved

from elisa.plugins.base.models.device import DevicesModel

from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)

from twisted.internet import task

from elisa.plugins.hal.hal import HALDevice
from elisa.plugins.hal.dbus_helper import call_async_dbus_method as dbus_call

class HALResource(ResourceProvider):
    """
    HAL resource provider. Provides volumes:// URI and sends messages on the
    bus when devices are plugged or unplugged (devices include CDs and DVDs).
    """
    log_category = "hal_resource"
    supported_uri='^volumes://'
    default_filter = ('removable', 'dvd', 'cdda')

    def initialize(self):

        dfr = super(HALResource, self).initialize()
        bus = common.application.bus

        self._volume_manager_config = {}
        self._bus = dbus.SystemBus()

        hal_manager = self._bus.get_object('org.freedesktop.Hal',
                                               '/org/freedesktop/Hal/Manager')

        self._hal_iface = dbus.Interface(hal_manager,
                                        'org.freedesktop.Hal.Manager')

        self._device_added_conn = \
            self._hal_iface.connect_to_signal('DeviceAdded',
                                             self._device_added_callback)
        self._device_removed_conn = \
            self._hal_iface.connect_to_signal('DeviceRemoved',
                                             self._device_removed_callback)

        self._current_deferred = []

        self.stop_volume_manager_monitoring()

        return dfr

    def clean(self):
        self._device_added_conn.remove()
        self._device_removed_conn.remove()
        self.restore_volume_manager_monitoring()

        dfr = super(HALResource, self).clean()

        def wait_for_current_deferred(param):
            dfr = DeferredList(self._current_deferred)
            dfr.addCallback(lambda ignored: param)
            return dfr

        dfr.addCallback(wait_for_current_deferred)

        return dfr

    def get(self, uri, context_model=None):
        """
        Simple method to retrieve volumes. You can access it with
        C{volumes://} and apply a filter parameter. If you specify it
        only these kind of volumes show up. Example::

        ::

          C{volumes://localhost/?filter=dvd,cdda}

        would only give you dvds and cddas. The three knows filters
        are: dvd, removable and cdda.

        The default is that all filters are applied (like
        filter=dvd,cdda,removable).

        In return you get a L{elisa.plugins.base.models.device.DevicesModel}
        """

        # FIXME: these filters sound wrong to me. And DVDs and audio CDs are
        # weird answers to a volumes:// query. We should use different uri
        # schemes instead of these filters.
        filter_string = uri.get_param('filter', None)

        if filter_string == None:
            filters = HALResource.default_filter
        else:
            filters = filter_string.split(',')

        model = DevicesModel()
        model.filter = filter


        def handle_device(hal_device):
            if hal_device.filter(filters):
                device_model = hal_device.create_model()
                model.devices.append(device_model)

        def iterate_devices(devices):
            for udi in devices:
                dfr = HALDevice.create(udi, self._bus)
                dfr.addCallback(handle_device)
                yield dfr

        # we only support retrieval of volume devices for now
        dfr = dbus_call(self._hal_iface.FindDeviceByCapability, 'volume')
        def on_devices_found(devices):
            return task.coiterate((iterate_devices(devices)))
        dfr.addCallback(on_devices_found)

        dfr.addCallback(lambda ignored: model)

        return model, dfr

    def stop_volume_manager_monitoring(self):
        """
        Neutralize some of the volume_manager monitoring settings so
        that the user won't see rhythmbox pop up when an iPod is
        inserted (for example).
        """
        # FIXME: implement this

    def restore_volume_manager_monitoring(self):
        """
        Restore the volume_manager gconf settings
        """
        # FIXME: implement this


    # send messages
    # new device by hal
    def _device_added_callback(self, udi):
        self.debug("device added: %r", udi)

        dfr = HALDevice.create(udi, self._bus)
        self._current_deferred.append(dfr)

        def send_message(hal_device):
            if hal_device.is_known_device():
                if hal_device.filter(HALResource.default_filter):
                    model = hal_device.create_model()
                    msg = NewDeviceDetected()
                    msg.udi = hal_device.udi
                    msg.model = model
                    common.application.bus.send_message(msg, self)
            else:
                common.application.bus.send_message(NewUnknownDevice(hal_device.udi), self)
            self._current_deferred.remove(dfr)
        def wait_for_mount(hal_device):
            if hal_device.needs_mounting() and not hal_device.is_ignored():
                dfr = hal_device.wait_for_mount(10)
                dfr.addCallback(lambda ignored: hal_device)
                return dfr
            else:
                return hal_device

        dfr.addCallback(wait_for_mount)
        dfr.addCallback(send_message)

    def _device_removed_callback(self, udi):
        common.application.bus.send_message(DeviceRemoved(str(udi)), self)

