#!/usr/bin/env python

# Copyright (C) 2005-2007 INdT - Instituto Nokia de Tecnologia
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

__author__ = "Artur Duque de Souza"
__author_email__ = "artur.souza@openbossa.org"
__license__ = "GPL"

import os
import dbus
import logging

from canolad.mount_table import MountTable

class Device(object):
    log = logging.getLogger("canola.daemon.haltab.device")

    def __init__(self, mger_tab, dev_uri, cb_mount_table=None):
        self.mger_tab = mger_tab
        self.dev_uri = dev_uri
        self.cb_mount_table = cb_mount_table

        self.block_device = None
        self.mount_point = None
        self.volume_fstype = None

        self.dev_obj = self.mger_tab.bus.get_object(
            "org.freedesktop.Hal", dev_uri, introspect=False)

        self.dev_iface = dbus.Interface(self.dev_obj, "org.freedesktop.Hal.Device")
        self.dev_iface.connect_to_signal("PropertyModified",
                                         self.change_property_signal)

    def create_dbus_array(self, string):
        ret = dbus.Array(string, signature=dbus.Signature("y"))
        return ret

    def change_property_signal(self, size, list_properties):
        # The device was mounted/unmounted

        try:
            last_mount_point = self.mount_point

            self.block_device = self.dev_iface.GetProperty("block.device")
            # XXX: dbus' mandatory conversion to utf8 of the path may cause
            # problems
            self.mount_point = str(self.dev_iface.GetProperty(
                "volume.mount_point", utf8_strings=True))
            self.volume_fstype = self.dev_iface.GetProperty("volume.fstype")
            self.volume_ismounted = \
                self.dev_iface.GetProperty("volume.is_mounted")

            self.log.debug("Device's properties are block_device: %s, "
                           "mount_point: %s, volume_fstype: %s,"
                           " volume_ismounted: %s",
                           self.block_device, self.mount_point,
                           self.volume_fstype, self.volume_ismounted)

            if self.volume_ismounted and self.mger_tab.cb_dev_added is not None:
                sig = self.create_dbus_array(self.mount_point)
                self.mger_tab.cb_dev_added(sig)

            if not self.volume_ismounted and last_mount_point and \
                    self.mger_tab.cb_dev_removed:
                sig = self.create_dbus_array(last_mount_point)
                self.mger_tab.cb_dev_removed(sig)
                self.mger_tab.mount_table.check_all_paths(
                    [last_mount_point], [last_mount_point], [last_mount_point])

        except Exception:
            self.log.debug("Could not get properties.")

        if self.cb_mount_table and self.block_device and \
           self.mount_point and self.volume_fstype:
            self.cb_mount_table(self.block_device,
                                self.mount_point, self.volume_fstype)
        return True


class Haltab(object):
    mount_file = "/etc/mtab"
    log = logging.getLogger("canola.daemon.haltab")

    def __init__(self, cb_dev_added=None, cb_dev_removed=None, cb_db_locked=None, cb_db_unlocked=None):
        self.cb_dev_added = cb_dev_added
        self.cb_dev_removed = cb_dev_removed
        self.bus = dbus.SystemBus()
        test_object = self.bus.get_object("org.freedesktop.DBus",
                                          "/org/freedesktop/DBus",
                                          introspect=False)
        test = dbus.Interface(test_object, "org.freedesktop.DBus")
        serv = test.ListNames()
        if "org.freedesktop.Hal" not in serv:
            raise Exception("Hal not detected")

        self.hal_object = self.bus.get_object("org.freedesktop.Hal",
                                              "/org/freedesktop/Hal/Manager",
                                              introspect=False)

        self.hal_manager = dbus.Interface(self.hal_object,
                                          "org.freedesktop.Hal.Manager")
        self._started_monitor = False
        self.devs = {}
        self.mount_table = MountTable(cb_db_locked, cb_db_unlocked)
        self.log.info("Started Canola-Daemon with HAL support")

    def start_monitoring(self):
        # take a look on mount_file to discover what is already mounted
        # and start monitoring
        if not self._started_monitor:
            self.hal_manager.connect_to_signal("DeviceAdded", self.dev_added)
            self.hal_manager.connect_to_signal("DeviceRemoved", self.dev_removed)
            # detect already mounted disks
            self.read_mount_file()
            self._started_monitor = True

    def read_mount_file(self):
        def parse_line(line):
            fields = line.split(" ")
            # dev, mount_point, type
            if fields[1].find("/media") >= 0:
                return (fields[0], fields[1], fields[2])
            return None

        mtab = open(self.mount_file)
        lines = mtab.readlines()
        mtab.close()

        self.log.debug("Reading mount file: %s", self.mount_file)
        for line in lines:
            dev = parse_line(line)
            if dev:
                dev_uri = self.hal_manager.FindDeviceStringMatch("volume.mount_point",
                                                                 dev[1])
                dev_uri = str(dev_uri[0])
                if not self.mount_table.has_key(dev_uri):
                    self.log.info("Going to add device: %s", dev_uri)
                    device = Device(self, dev_uri, self.update_mount_table)
                    device.block_device = dev[0]
                    device.mount_point = dev[1]
                    device.volume_fstype = dev[2]
                    self.devs[dev_uri] = device
                    self.mount_table[dev_uri] = dev

        self.log.info("Detected devices: %s", self.mount_table)
        return True

    def update_mount_table(self, block_device, mount_point, volume_fstype):
        key = self.hal_manager.FindDeviceStringMatch("volume.mount_point",
                                                     mount_point)
        if len(key) > 0:
            key = str(key[0])
            if not self.mount_table.has_key(key):
                t = (str(block_device), str(mount_point), str(volume_fstype))
                self.mount_table[str(key)] = t
                self.log.debug("Mount table: %s", self.mount_table)
        return True

    def dev_added(self, dev_uri):
        # Added device
        dev_uri = str(dev_uri)
        if not self.mount_table.has_key(dev_uri):
            self.devs[dev_uri] = Device(self, dev_uri, self.update_mount_table)
            self.log.info("Added device: %s", dev_uri)
        return True

    def dev_removed(self, dev_uri):
        # Removed device
        dev_uri = str(dev_uri)
        dev = self.mount_table.pop(dev_uri, None)
        self.devs.pop(dev_uri, None)
        if dev:
            self.log.info("Removed device: %s", dev[1])
        return True
