#! /usr/bin/env python3
# -.- coding: utf-8 -.-

"""
    Arista Input Device Discovery
    =============================
    A set of tools to discover DVD-capable devices and Video4Linux devices that
    emit signals when disks that contain video are inserted or webcames / tuner
    cards are plugged in using udev.

    http://github.com/nzjrs/python-gudev/blob/master/test.py
    http://www.kernel.org/pub/linux/utils/kernel/hotplug/gudev/GUdevDevice.html
    
    License
    -------
    Copyright 2008 - 2011 Daniel G. Taylor <dan@programmer-art.org>
    
    This file is part of Arista.

    Arista is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as 
    published by the Free Software Foundation, either version 2.1 of
    the License, or (at your option) any later version.

    Arista 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with Arista.  If not, see
    <http://www.gnu.org/licenses/>.

    2013 Christian Schaller <uraeus@linuxrising.org>
    - Ported to Python3 for Transmageddon
"""

import gettext

from gi.repository import GObject
from gi.repository import GUdev

_ = gettext.gettext

class InputSource(object):
    """
        A simple object representing an input source.
    """
    def __init__(self, device):
        """
            Create a new input device.
            
            @type device: gudev.Device
            @param device: The device that we are using as an input source
        """
        self.device = device

    @property
    def nice_label(self):
        """
            Get a nice label for this device.
            
            @rtype: str
            @return: The label, in this case the product name
        """
        return self.path
    
    @property
    def path(self):
        """
            Get the device block in the filesystem for this device.
            
            @rtype: string
            @return: The device block, such as "/dev/cdrom".
        """
        return self.device.get_device_file()

class DVDDevice(InputSource):
    """
        A simple object representing a DVD-capable device.
    """
    @property
    def media(self):
        """
            Check whether media is in the device.
            
            @rtype: bool
            @return: True if media is present in the device.
        """
        return self.device.has_property("ID_FS_TYPE")

    @property
    def nice_label(self):
        if self.device.has_property("ID_FS_LABEL"):
            label = self.device.get_property("ID_FS_LABEL")
            return " ".join([word.capitalize() for word in label.split("_")])
        else:
            return self.device.get_property("ID_MODEL")

class V4LDevice(InputSource):
    """
        A simple object representing a Video 4 Linux device.
    """
    @property
    def nice_label(self):
        return self.device.get_sysfs_attr("name")

    @property
    def version(self):
        """
            Get the video4linux version of this device.
        """
        if self.device.has_property("ID_V4L_VERSION"):
            return self.device.get_property("ID_V4L_VERSION")
        else:
            # Default to version 2
            return "2"

class InputFinder(GObject.GObject):
    """
        An object that will find and monitor DVD-capable devices on your 
        machine and emit signals when video disks are inserted / removed.
        
        Signals:
        
         - disc-found(InputFinder, DVDDevice, label)
         - disc-lost(InputFinder, DVDDevice, label)
         - v4l-capture-found(InputFinder, V4LDevice)
         - v4l-capture-lost(InputFinder, V4LDevice)
    """
    
    __gsignals__ = {
        "disc-found": (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, 
                       (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT)),
        "disc-lost": (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, 
                      (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT)),
        "v4l-capture-found": (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE,
                              (GObject.TYPE_PYOBJECT,)),
        "v4l-capture-lost": (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE,
                             (GObject.TYPE_PYOBJECT,)),
        
    }
    
    def __init__(self):
        """
            Create a new DVDFinder and attach to the udev system to listen for
            events.
        """
        GObject.GObject.__init__(self)

        self.client = GUdev.Client(subsystems=["video4linux", "block"])
        
        self.drives = {}
        self.capture_devices = {}

        for device in self.client.query_by_subsystem("video4linux"):
            block = device.get_device_file()
            self.capture_devices[block] = V4LDevice(device)

        for device in self.client.query_by_subsystem("block"):
            if device.has_property("ID_CDROM"):
                block = device.get_device_file()
                self.drives[block] = DVDDevice(device)

        self.client.connect("uevent", self.event)

    def event(self, client, action, device):
        """
            Handle a udev event.
        """
        return {
            "add": self.device_added,
            "change": self.device_changed,
            "remove": self.device_removed,
        }.get(action, lambda x,y: None)(device, device.get_subsystem())
    
    def device_added(self, device, subsystem):
        """
            Called when a device has been added to the system.
        """
        if subsystem == "video4linux":
            block = device.get_device_file()
            self.capture_devices[block] = V4LDevice(device)
            self.emit("v4l-capture-found", self.capture_devices[block])

    def device_changed(self, device, subsystem):
        """
            Called when a device has changed. If the change represents a disc
            being inserted or removed, fire the disc-found or disc-lost signals
            respectively.
        """
        if subsystem == "block" and device.has_property("ID_CDROM"):
            block = device.get_device_file()
            if block not in self.drives:
                self.drives[block] = DVDDevice(device)
            dvd_device = self.drives[block]
            media_changed = dvd_device.media != device.has_property("ID_FS_TYPE")
            dvd_device.device = device
            if media_changed:
                if dvd_device.media:
                    self.emit("disc-found", dvd_device, dvd_device.nice_label)
                else:
                    self.emit("disc-lost", dvd_device, dvd_device.nice_label)
    
    def device_removed(self, device, subsystem):
        """
            Called when a device has been removed from the system.
        """
        pass

GObject.type_register(InputFinder)

if __name__ == "__main__":
    # Run a test to print out DVD-capable devices and whether or not they
    # have video disks in them at the moment.
    GObject.threads_init()
    
    def found(finder, device, label):
        print(device.path + ": " + label)
    
    def lost(finder, device, label):
        print(device.path + ": " + _("Not mounted."))
    
    finder = InputFinder()
    finder.connect("disc-found", found)
    finder.connect("disc-lost", lost)
    
    for device, drive in finder.drives.items():
        print("drive.nice_label" + ": " + device)
    
    for device, capture in finder.capture_devices.items():
        print(capture.nice_label + " V4Lv" + str(capture.version) + ": " + device)
    
    loop = GObject.MainLoop()
    loop.run()

