#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# cdemu: command-line CDEmu client
# Copyright (C) 2006-2014 Rok Mandeljc
#
# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.

from __future__ import print_function

import argparse
import collections
import datetime
import sys
import os.path
import getpass

import gi
from gi.repository import GLib
from gi.repository import Gio


try:
    import configparser
except ImportError:
    import ConfigParser as configparser

import gettext


# *** Globals ***
app_name = "cdemu"
app_version = "3.2.3"
supported_daemon_interface_version = [ 7, 0 ]

# I18n
if sys.version_info[0] < 3:
    # Prior to python3, we need to explicitly enable unicode strings
    gettext.install(app_name, unicode=True)
else:
    gettext.install(app_name)


# Set process name
# "linux2" is for python2, "linux" is for python3
if sys.platform == "linux" or sys.platform == "linux2":
    # Process name must be a byte string...
    if isinstance(app_name, bytes):
        app_name_bytes = app_name
    else:
        app_name_bytes = app_name.encode('utf-8')

    try:
        import ctypes
        libc = ctypes.CDLL("libc.so.6")
        libc.prctl(15, app_name_bytes, 0, 0, 0) # 15 = PR_SET_NAME
    except Exception:
        pass


# String to boolean conversion helper
def str2bool (value):
    return value.lower() in ("yes", "true", "t", "1")

########################################################################
#                            Terminal colors                           #
########################################################################
# attrs is a comma separated list of integers, where valid values include
# foreground: 30-37, background: 40-47, reset: 0, bold: 1. (ECMA-48 / VT102)
def set_terminal_color(stream, *attrs):
    if stream.isatty():
        attrs_str = ";".join(map(str, attrs))
        stream.write("\x1b[%sm" % (attrs_str))

def print_header (message):
    set_terminal_color(sys.stdout, 1, 34)
    print(message, file=sys.stdout)
    set_terminal_color(sys.stdout, 0)

def print_error (message):
    set_terminal_color(sys.stderr, 1, 31)
    print(_("ERROR: %s") % (message), file=sys.stderr)
    set_terminal_color(sys.stderr, 0)

def print_warning (message):
    set_terminal_color(sys.stdout, 1, 33)
    print(_("WARNING: %s") % (message), file=sys.stdout)
    set_terminal_color(sys.stdout, 0)


########################################################################
#                                 Error                                #
########################################################################
class CDEmuError (Exception): pass
class CDEmuParserError (Exception): pass

########################################################################
#                              Load device                             #
########################################################################
class cmd_load (object):
    def __init__ (self, subparsers):
        name = "load"
        description_msg = _("")
        help_msg = _("loads an image to the device")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))
        parser.add_argument("image_file", type=str, nargs="+", help=_("image file(s)"))
        parser.add_argument("--password", type=str, help=_("password for encrypted images"))
        parser.add_argument("--encoding", type=str, help=_("encoding for text-based images"))

    def __call__ (self, proxy, arguments):
        # We need to pass absolute filenames to daemon
        filenames = [os.path.abspath(f) for f in arguments.image_file]

        # Gather parser parameters into a dictionary
        parser_params = {}
        if hasattr(arguments, "encoding"):
            parser_params["encoding"] = GLib.Variant("s", arguments.encoding)
        if hasattr(arguments, "password"):
            parser_params["password"] = GLib.Variant("s", arguments.password)

        # Particular device vs. any device
        if arguments.device == "any":
            try:
                num_devices = proxy.GetNumberOfDevices()
            except GLib.Error as e:
                raise CDEmuError(_("Failed to get number of devices: %s") % (e))

            for device in range(num_devices):
                # Device's status
                try:
                    status = proxy.DeviceGetStatus(device)
                except GLib.Error as e:
                    print_warning(_("Failed to get status of device %i: %s") % (device, e))
                    continue

                # If device is already loaded, skip it
                if status[0]:
                    continue

                # Load device
                return self.load_device(proxy, device, filenames, parser_params)
            else:
                # If we're here, it means we didn't get an empty device
                raise CDEmuError(_("No empty device found"))
        else:
            try:
                device = int(arguments.device, 0)
            except ValueError:
                raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

            return self.load_device(proxy, device, filenames, parser_params)

    # Device loading with password query support
    def load_device (self, proxy, device, filenames, params={}):
        # Try to load it
        try:
            proxy.DeviceLoad(device, filenames, params)
        except GLib.Error as e:
            if "net.sf.cdemu.CDEmuDaemon.errorMirage.EncryptedImage" in str(e):
                # We need password
                print(_("The image you are trying to load is encrypted."))
                password = getpass.getpass(_("Password: ")) # Append password to params
                params["password"] = GLib.Variant("s", password)
                return load_device(device, filenames, params)
            else:
                raise CDEmuError(_("Failed to load image: %s") % (e))

########################################################################
#                              Create blank                            #
########################################################################
class cmd_create_blank (object):
    def __init__ (self, subparsers):
        name = "create-blank"
        description_msg = _("")
        help_msg = _("load blank/recordable image to the device")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))
        parser.add_argument("image_file", type=str, help=_("image filename/basename"))
        parser.add_argument("--writer-id", type=str, nargs="?", required=True, help=_("ID of image writer to use"))
        parser.add_argument("--medium-type", type=str, nargs="?", choices=['cdr74', 'cdr80', 'cdr90', 'cdr99', 'dvd+r', 'bdr'], help=_("medium type"))
        parser.add_argument("--param", type=str, nargs="?", action='append', help=_("additional writer parameter(s)"))

    def __call__ (self, proxy, arguments):
        # We need to pass absolute filename/basename to daemon
        filename = os.path.abspath(arguments.image_file)

        # Gather writer parameters into a dictionary
        parameters = {}
        if hasattr(arguments, "writer_id"):
            parameters["writer-id"] = GLib.Variant("s", arguments.writer_id)
        if hasattr(arguments, "medium_type"):
            parameters["medium-type"] = GLib.Variant("s", arguments.medium_type)
        if hasattr(arguments, "param"):
            # Process parameters
            writer_parameters = self.process_writer_parameters(arguments.param)
            # Validate parameters using writer's parameter sheet
            writer_parameters = self.validate_writer_parameters(proxy, arguments.writer_id, writer_parameters)
            # Set to parameters dictionary that we will send to daemon
            parameters.update(writer_parameters)

        # Particular device vs. any device
        if arguments.device == "any":
            try:
                num_devices = proxy.GetNumberOfDevices()
            except GLib.Error as e:
                raise CDEmuError(_("Failed to get number of devices: %s") % (e))

            for device in range(num_devices):
                # Device's status
                try:
                    status = proxy.DeviceGetStatus(device)
                except GLib.Error as e:
                    print_warning(_("Failed to get status of device %i: %s") % (device, e))
                    continue

                # If device is already loaded, skip it
                if status[0]:
                    continue

                # Load device
                return self.load_device(proxy, device, filename, parameters)
            else:
                # If we're here, it means we didn't get an empty device
                raise CDEmuError(_("No empty device found"))
        else:
            try:
                device = int(arguments.device, 0)
            except ValueError:
                raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

            return self.load_device(proxy, device, filename, parameters)

    # Device loading
    def load_device (self, proxy, device, filenames, params={}):
        # Try to load it
        try:
            proxy.DeviceCreateBlank(device, filenames, params)
        except GLib.Error as e:
            raise CDEmuError(_("Failed to load blank image: %s") % (e))

    # Image writer parameter processing
    def process_writer_parameters (self, parameter_strings):
        parameters = {}

        # In general, we can be given multiple strings, one for each
        # command-line argument...
        for parameter_string in parameter_strings:
            # ... and to make things interesting, each string can
            # contain several "key=value" pairs, separated by semi-colon
            parameter_tokens = parameter_string.split(";")

            for parameter_token in parameter_tokens:
                # Split into key and value
                try:
                    (key, value) = parameter_token.split("=")
                except ValueError as e:
                    raise CDEmuError(_("Parameter token '%s' is not a key=value pair!") % parameter_token)

                # Strip whitespaces and insert into dictionary
                parameters[key.strip()] = value.strip()

        return parameters

    # Writer parameter validation
    def validate_writer_parameters (self, proxy, writer_id, parameters):
        # Grab writer's parameter sheet
        try:
            parameter_sheet = proxy.EnumWriterParameters(writer_id)
        except GLib.Error as e:
            raise CDEmuError(_("Failed to get parameter sheet for writer: %s") % (e))

        # Convert the XML tree into a dictionary for convenience
        valid_parameters = {}
        for parameter_entry in parameter_sheet:
            valid_parameters[parameter_entry[0]] = parameter_entry

        # Now, go over given parameters and validate them
        # NOTE: we need to use list(parameters.items()) because we may
        # change the dictionary (pop() call during validation), and that
        # raises error in python3
        for key,value in list(parameters.items()):
            try:
                parameter_entry = valid_parameters[key]
            except KeyError as e:
                print_warning(_("Parameter '%s' not found in writer's parameter sheet!") % (key))
                parameters.pop(key, '') # Remove from dictionary
                continue

            # Determine type
            if len(parameter_entry[4]):
                # Enum; no conversion to be done, but check if value is
                # on the list of valid values, and if not, print a warning
                for enum_value in parameter_entry[4]:
                    if value == enum_value:
                        parameters[key] = GLib.Variant("s", value)
                        break
                else:
                    print_warning(_("Invalid value '%s' given for parameter '%s'!") % (value, key))
            elif isinstance(parameter_entry[3], str):
                # String
                parameters[key] = GLib.Variant("s", value)
            elif isinstance(parameter_entry[3], bool):
                # Boolean; convert string value to boolean
                parameters[key] = GLib.Variant("b", str2bool(value))
            elif isinstance(parameter_entry[3], int):
                # Convert string value to integer
                try:
                    parameters[key] = GLib.Variant("i", int(value, 0))
                except ValueError:
                    raise CDEmuError(_("String '%s' given as value for parameter '%s' is not a number!") % (value, key))
            else:
                print_warning(_("Unhandled parameter type '%s' declared for parameter '%s'!") % (type(parameter[3]), key))
                continue

        return parameters


########################################################################
#                             Unload device                            #
########################################################################
class cmd_unload (object):
    def __init__ (self, subparsers):
        name = "unload"
        description_msg = _("")
        help_msg = _("unloads the device")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))

    def __call__ (self, proxy, arguments):
        # Particular device vs. all devices
        if arguments.device == "all":
            try:
                num_devices = proxy.GetNumberOfDevices()
            except GLib.Error as e:
                raise CDEmuError(_("Failed to get number of devices: %s") % (e))

            for device in range(num_devices):
                try:
                    proxy.DeviceUnload(device)
                except GLib.Error as e:
                    print_warning(_("Failed to unload device %i: %s") % (device, e))
                    continue
        else:
            try:
                device = int(arguments.device, 0)
                proxy.DeviceUnload(device)
            except GLib.Error as e:
                raise CDEmuError(_("Failed to unload device %i: %s") % (device, e))
            except ValueError:
                raise CDEmuError(_("String '%s' is not a number") % (arguments.device))


########################################################################
#                            Display status                            #
########################################################################
class cmd_display_status (object):
    def __init__ (self, subparsers):
        name = "status"
        description_msg = _("")
        help_msg = _("displays the devices' status")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        # Print status for all devices
        try:
            num_devices = proxy.GetNumberOfDevices()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to get number of devices: %s") % (e))

        print_header(_("Devices' status:"))
        print("%-5s %-10s %s" % (_("DEV"), _("LOADED"), _("FILENAME")))
        for device in range (num_devices):
            try:
                [loaded, filenames] = proxy.DeviceGetStatus(device)
            except GLib.Error as e:
                print_warning(_("Failed to get status of device %i: %s") % (device, e))
                continue

            if not loaded:
                filenames = [ "" ]

            # First line is for all device's data, the rest are for additional filenames
            print("%-5s %-10s %s" % (device, loaded, filenames[0]))
            for filename in filenames[1:]:
                print("%-5s %-10s %s" % ("", "", filename))


########################################################################
#                            Device mapping                            #
########################################################################
class cmd_device_mapping (object):
    def __init__ (self, subparsers):
        name = "device-mapping"
        description_msg = _("")
        help_msg = _("displays the device mapping information")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        # Print device mapping for all devices
        try:
            num_devices = proxy.GetNumberOfDevices()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to get number of devices: %s") % (e))

        print_header(_("Device mapping:"))
        print("%-5s %-15s %-15s" % (_("DEV"), _("SCSI CD-ROM"), _("SCSI generic")))
        for device in range (num_devices):
            try:
                [dev_sr, dev_sg] = proxy.DeviceGetMapping(device)
            except GLib.Error as e:
                print_warning(_("Failed to get device mapping of device %i: %s") % (device, e))
                continue

            print("%-5s %-15s %-15s" % (device, dev_sr, dev_sg))


########################################################################
#                           Daemon debug mask                          #
########################################################################
class cmd_daemon_debug_mask (object):
    def __init__ (self, subparsers):
        name = "daemon-debug-mask"
        description_msg = _("")
        help_msg = _("displays/sets daemon debug mask")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))
        parser.add_argument("new_value", nargs="?", type=str, help=_("new value"))
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        # Retrieve list of valid masks (for encoding/decoding)
        mask_mapping = self.__get_debug_mask_mapping(proxy)

        # Set daemon debug mask
        if hasattr(arguments, "new_value"):
            # Convert the input value to mask
            mask = self.__convert_input_to_mask(arguments.new_value, mask_mapping)

            # Set the mask
            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print(_("Setting daemon debug mask of all devices to 0x%X.") % (mask))
                for device in range(num_devices):
                    try:
                        proxy.DeviceSetOption(device, "daemon-debug-mask", GLib.Variant("i", mask))
                    except GLib.Error as e:
                        print_warning(_("Failed to set daemon debug mask of device %i to 0x%X: %s") % (device, mask, e))
                        continue
            else:
                try:
                    device = int(arguments.device, 0)
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Setting daemon debug mask of device %i to 0x%X.") % (device, mask))
                try:
                    proxy.DeviceSetOption(device, "daemon-debug-mask", GLib.Variant("i", mask))
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to set daemon debug mask of device %i to 0x%X: %s") % (device, mask, e))

        # Get daemon debug mask
        else:
            # Particular device vs. all devices
            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print_header(_("Devices' daemon debug masks:"))
                print("%-5s %-10s   %s" % (_("DEV"), _("DEBUG MASK"), _("MEANING")))

                for device in range(num_devices):
                    try:
                        mask = proxy.DeviceGetOption(device, "daemon-debug-mask")
                    except GLib.Error as e:
                        print_warning(_("Failed to get daemon debug mask of device %i: %s") % (device, e))
                        continue

                    mask_meaning = self.__convert_mask_to_string(mask, mask_mapping)

                    print("%-5s 0x%08X   %s" % (device, mask, mask_meaning))
            else:
                try:
                    device = int(arguments.device, 0)
                    mask = proxy.DeviceGetOption(device, "daemon-debug-mask")
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get daemon debug mask of device %i: %s") % (device, e))
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                mask_meaning = self.__convert_mask_to_string(mask, mask_mapping)

                print(_("Daemon debug mask of device %i: 0x%X (%s)") % (device, mask, mask_meaning))

    def __convert_input_to_mask (self, input_str, mapping):
        output_mask = 0

        # Split input string on pipe (|)
        tokens = input_str.split("|")
        for token in tokens:
            token = token.strip()

            try:
                if token in mapping:
                    val = mapping[token]
                else:
                    val = int(token, 0)
            except ValueError:
                raise CDEmuError(_("String '%s' is not a number nor was it found in the list of valid debug mask identifiers") % (token))

            output_mask |= val

        return output_mask

    def __convert_mask_to_string (self, value, mapping):
        output_list = []

        for mask_name, mask_value in mapping.items():
            if value & mask_value:
                output_list.append(mask_name)

        return "|".join(output_list)

    def __get_debug_mask_mapping (self, proxy):
        mapping = collections.OrderedDict()

        try:
            debug_masks = proxy.EnumDaemonDebugMasks()
            for debug_mask in debug_masks:
                mapping[debug_mask[0]] = debug_mask[1]
        except GLib.Error as e:
            print_warning(_("Failed to enumerate supported daemon debug masks: %s") % (e))

        return mapping


########################################################################
#                          Library debug mask                          #
########################################################################
class cmd_library_debug_mask (object):
    def __init__ (self, subparsers):
        name = "library-debug-mask"
        description_msg = _("")
        help_msg = _("displays/sets library debug mask")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))
        parser.add_argument("new_value", nargs="?", type=str, help=_("new value"))

    def __call__ (self, proxy, arguments):
        # Retrieve list of valid masks (for encoding/decoding)
        mask_mapping = self.__get_debug_mask_mapping(proxy)

        # Set debug mask
        if hasattr(arguments, "new_value"):
            # Convert the input value to mask
            mask = self.__convert_input_to_mask(arguments.new_value, mask_mapping)

            # Set the mask
            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print(_("Setting library debug mask of all devices to 0x%X.") % (mask))
                for device in range(num_devices):
                    try:
                        proxy.DeviceSetOption(device, "library-debug-mask", GLib.Variant("i", mask))
                    except GLib.Error as e:
                        print_warning(_("Failed to set library debug mask of device %i to 0x%X: %s") % (device, mask, e))
                        continue
            else:
                try:
                    device = int(arguments.device, 0)
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Setting library debug mask of device %i to 0x%X.") % (device, mask))
                try:
                    proxy.DeviceSetOption(device, "library-debug-mask", GLib.Variant("i", mask))
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to set library debug mask of device %i to 0x%X: %s") % (device, mask, e))

        # Get debug mask
        else:
            # Particular device vs. all devices
            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print_header(_("Devices' library debug masks:"))
                print("%-5s %-10s   %s" % (_("DEV"), _("DEBUG MASK"), _("MEANING")))

                for device in range(num_devices):
                    try:
                        mask = proxy.DeviceGetOption(device, "library-debug-mask")
                    except GLib.Error as e:
                        print_warning(_("Failed to get library debug mask of device %i: %s") % (device, e))

                    mask_meaning = self.__convert_mask_to_string(mask, mask_mapping)

                    print("%-5s 0x%08X   %s" % (device, mask, mask_meaning))
            else:
                try:
                    device = int(arguments.device, 0)
                    mask = proxy.DeviceGetOption(device, "library-debug-mask")
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get library debug mask of device %i: %s") % (device, e))
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (mask))

                mask_meaning = self.__convert_mask_to_string(mask, mask_mapping)

                print(_("Library debug mask of device %i: 0x%X (%s)") % (device, mask, mask_meaning))

    def __convert_input_to_mask (self, input_str, mapping):
        output_mask = 0

        # Split input string on pipe (|)
        tokens = input_str.split("|")
        for token in tokens:
            token = token.strip()

            try:
                if token in mapping:
                    val = mapping[token]
                else:
                    val = int(token, 0)
            except ValueError:
                raise CDEmuError(_("String '%s' is not a number nor was it found in the list of valid debug mask identifiers") % (token))

            output_mask |= val

        return output_mask

    def __convert_mask_to_string (self, value, mapping):
        output_list = []

        for mask_name, mask_value in mapping.items():
            if value & mask_value:
                output_list.append(mask_name)

        return "|".join(output_list)

    def __get_debug_mask_mapping (self, proxy):
        mapping = collections.OrderedDict()

        try:
            debug_masks = proxy.EnumLibraryDebugMasks()
            for debug_mask in debug_masks:
                mapping[debug_mask[0]] = debug_mask[1]
        except GLib.Error as e:
            print_warning(_("Failed to enumerate supported library debug masks: %s") % (e))

        return mapping


########################################################################
#                            DPM emulation                            #
########################################################################
class cmd_dpm_emulation (object):
    def __init__ (self, subparsers):
        name = "dpm-emulation"
        description_msg = _("")
        help_msg = _("displays/sets DPM emulation flag")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))
        parser.add_argument("new_value", nargs="?", type=str, help=_("new value"))

    def __call__ (self, proxy, arguments):
        # Set DPM emulation flag
        if hasattr(arguments, "new_value"):
            try:
                enabled = str2bool(arguments.new_value)
            except ValueError:
                raise CDEmuError(_("String '%s' is not a number") % (arguments.new_value))

            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print(_("Setting DPM emulation flag of all devices to %i.") % (enabled))
                for device in range(num_devices):
                    try:
                        proxy.DeviceSetOption(device, "dpm-emulation", GLib.Variant("b", enabled))
                    except GLib.Error as e:
                        print_warning(_("Failed to set DPM emulation flag of device %i to %i: %s") % (device, enabled, e))
                        continue
            else:
                try:
                    device = int(arguments.device, 0)
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Setting DPM emulation flag of device %i to %i.") % (device, enabled))
                try:
                    proxy.DeviceSetOption(device, "dpm-emulation", GLib.Variant("b", enabled))
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to set DPM emulation flag of device %i to %i: %s") % (device, enabled, e))

        # Get DPM emulation flag
        else:
            # Particular device vs. all devices
            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print_header(_("Devices' DPM emulation flag:"))
                print("%-5s %-10s" % (_("DEV"), _("ENABLED")))

                for device in range(num_devices):
                    try:
                        enabled = proxy.DeviceGetOption(device, "dpm-emulation")
                    except GLib.Error as e:
                        print_warning(_("Failed to get DPM emulation flag of device %i: %s") % (device, e))
                        continue

                    print("%-5s %i" % (device, enabled))
            else:
                try:
                    device = int(arguments.device, 0)
                    enabled = proxy.DeviceGetOption(device, "dpm-emulation")
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get DPM emulation flag of device %i: %s") % (device, e))
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("DPM emulation flag of device %i: %i") % (device, enabled))


########################################################################
#                        Transfer rate emulation                       #
########################################################################
class cmd_tr_emulation (object):
    def __init__ (self, subparsers):
        name = "tr-emulation"
        description_msg = _("")
        help_msg = _("displays/sets transfer rate emulation flag")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))
        parser.add_argument("new_value", nargs="?", type=str, help=_("new value"))

    def __call__ (self, proxy, arguments):
        # Set TR emulation flag
        if hasattr(arguments, "new_value"):
            try:
                enabled = str2bool(arguments.new_value)
            except ValueError:
                raise CDEmuError(_("String '%s' is not a number") % (arguments[1]))

            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print(_("Setting transfer rate emulation flag of all devices to %i.") % (enabled))
                for device in range(num_devices):
                    try:
                        proxy.DeviceSetOption(device, "tr-emulation", GLib.Variant("b", enabled))
                    except GLib.Error as e:
                        print_warning(_("Failed to set transfer rate emulation flag of device %i to %i: %s") % (device, enabled, e))
                        continue
            else:
                try:
                    device = int(arguments.device, 0)
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Setting transfer rate emulation flag of device %i to %i.") % (device, enabled))
                try:
                    proxy.DeviceSetOption(device, "tr-emulation", GLib.Variant("b", enabled))
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to set transfer rate emulation flag of device %i to %i: %s") % (device, enabled, e))

        # Get TR emulation flag
        else:
            # Particular device vs. all devices
            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print(_("Devices' transfer rate emulation flag:"))
                print("%-5s %-10s" % (_("DEV"), _("ENABLED")))

                for device in range(num_devices):
                    try:
                        enabled = proxy.DeviceGetOption(device, "tr-emulation")
                    except GLib.Error as e:
                        print_warning(_("Failed to get transfer rate emulation flag of device %i: %s") % (device, e))
                        continue

                    print("%-5s %i" % (device, enabled))
            else:
                try:
                    device = int(arguments.device, 0)
                    enabled = proxy.DeviceGetOption(device, "tr-emulation")
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get transfer rate emulation flag of device %i: %s") % (device, e))
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Transfer rate emulation flag of device %i: %i") % (device, enabled))


########################################################################
#                         Bad sector emulation                         #
########################################################################
class cmd_bad_sector_emulation (object):
    def __init__ (self, subparsers):
        name = "bad-sector-emulation"
        description_msg = _("")
        help_msg = _("displays/sets bad sector emulation flag")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))
        parser.add_argument("new_value", nargs="?", type=str, help=_("new value"))

    def __call__ (self, proxy, arguments):
        # Set bad sector emulation flag
        if hasattr(arguments, "new_value"):
            try:
                enabled = str2bool(arguments.new_value)
            except ValueError:
                raise CDEmuError(_("String '%s' is not a number") % (arguments[1]))

            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print(_("Setting bad sector emulation flag of all devices to %i.") % (enabled))
                for device in range(num_devices):
                    try:
                        proxy.DeviceSetOption(device, "bad-sector-emulation", GLib.Variant("b", enabled))
                    except GLib.Error as e:
                        print_warning(_("Failed to set bad sector emulation flag of device %i to %i: %s") % (device, enabled, e))
                        continue
            else:
                try:
                    device = int(arguments.device, 0)
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Setting bad sector emulation flag of device %i to %i.") % (device, enabled))
                try:
                    proxy.DeviceSetOption(device, "bad-sector-emulation", GLib.Variant("b", enabled))
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to set bad sector emulation flag of device %i to %i: %s") % (device, enabled, e))

        # Get bad sector emulation flag
        else:
            # Particular device vs. all devices
            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print_header(_("Devices' bad sector emulation flag:"))
                print("%-5s %-10s" % (_("DEV"), _("ENABLED")))

                for device in range(num_devices):
                    try:
                        enabled = proxy.DeviceGetOption(device, "bad-sector-emulation")
                    except GLib.Error as e:
                        print_warning(_("Failed to get bad sector emulation flag of device %i: %s") % (device, e))
                        continue

                    print("%-5s %i" % (device, enabled))
            else:
                try:
                    device = int(arguments.device, 0)
                    enabled = proxy.DeviceGetOption(device, "bad-sector-emulation")
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get bad sector emulation flag of device %i: %s") % (device, e))
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Bad sector emulation flag of device %i: %i") % (device, enabled))


########################################################################
#                            DVD report CSS                            #
########################################################################
class cmd_dvd_report_css (object):
    def __init__ (self, subparsers):
        name = "dvd-report-css"
        description_msg = _("")
        help_msg = _("displays/sets DVD report CSS/CPPM flag")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))
        parser.add_argument("new_value", nargs="?", type=str, help=_("new value"))

    def __call__ (self, proxy, arguments):
        # Set DVD report CSS flag
        if hasattr(arguments, "new_value"):
            try:
                enabled = str2bool(arguments.new_value)
            except ValueError:
                raise CDEmuError(_("String '%s' is not a number") % (arguments[1]))

            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print(_("Setting DVD report CSS/CPPM flag of all devices to %i.") % (enabled))
                for device in range(num_devices):
                    try:
                        proxy.DeviceSetOption(device, "dvd-report-css", GLib.Variant("b", enabled))
                    except GLib.Error as e:
                        print_warning(_("Failed to set DVD report CSS/CPPM flag of device %i to %i: %s") % (device, enabled, e))
                        continue
            else:
                try:
                    device = int(arguments.device, 0)
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Setting DVD report CSS/CPPM flag of device %i to %i.") % (device, enabled))
                try:
                    proxy.DeviceSetOption(device, "dvd-report-css", GLib.Variant("b", enabled))
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to set DVD report CSS/CPPM flag of device %i to %i: %s") % (device, enabled, e))

        # Get DVD report CSS flag
        else:
            # Particular device vs. all devices
            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print_header(_("Devices' DVD report CSS/CPPM flag:"))
                print("%-5s %-10s" % (_("DEV"), _("ENABLED")))

                for device in range(num_devices):
                    try:
                        enabled = proxy.DeviceGetOption(device, "dvd-report-css")
                    except GLib.Error as e:
                        print_warning(_("Failed to get DVD report CSS/CPPM flag of device %i: %s") % (device, e))
                        continue

                    print("%-5s %i" % (device, enabled))
            else:
                try:
                    device = int(arguments.device, 0)
                    enabled = proxy.DeviceGetOption(device, "dvd-report-css")
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get DVD report CSS/CPPM flag of device %i: %s") % (device, e))
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("DVD report CSS/CPPM flag of device %i: %i") % (device, enabled))


########################################################################
#                               Device ID                              #
########################################################################
class cmd_device_id (object):
    def __init__ (self, subparsers):
        name = "device-id"
        description_msg = _("")
        help_msg = _("displays/sets device ID")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("device", type=str, help=_("device"))
        parser.add_argument("new_id", nargs='*', type=str, help=_("new device ID: vendor ID, product ID, revision and vendor-specific string"))

    def __call__ (self, proxy, arguments):
        # Set device ID
        if hasattr(arguments, "new_id"):
            device_id = tuple(arguments.new_id)

            if len(device_id) != 4:
                raise CDEmuError(_("New device ID must consist of exactly four strings!"))

            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print(_("Setting device ID of all devices to %s.") % (device_id))
                for device in range(num_devices):
                    try:
                        proxy.DeviceSetOption(device, "device-id", GLib.Variant("(ssss)", device_id))
                    except GLib.Error as e:
                        print_warning(_("Failed to set device ID of device %i to %s: %s") % (device, device_id, e))
                        continue
            else:
                try:
                    device = int(arguments.device, 0)
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Setting device ID of device %i to %s.") % (device, device_id))
                try:
                    proxy.DeviceSetOption(device, "device-id", GLib.Variant("(ssss)", device_id))
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to set device ID of device %i to %s: %s") % (device, device_id, e))


        # Get device ID
        else:
            # Particular device vs. all devices
            if arguments.device == "all":
                try:
                    num_devices = proxy.GetNumberOfDevices()
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get number of devices: %s") % (e))

                print_header(_("Devices' IDs:"))
                print("%-5s %s" % (_("DEV"), _("DEVICE ID")))

                for device in range(num_devices):
                    try:
                        values = proxy.DeviceGetOption(device, "device-id")
                        device_id = list(map(str, values))
                    except GLib.Error as e:
                        print_warning(_("Failed to get device ID of device %i: %s") % (device, e))
                        continue

                    print("%-5s %s" % (device, device_id))
            else:
                try:
                    device = int(arguments.device, 0)
                    values = proxy.DeviceGetOption(device, "device-id")
                    device_id = list(map(str, values))
                except GLib.Error as e:
                    raise CDEmuError(_("Failed to get device ID of device %i: %s") % (device, e))
                except ValueError:
                    raise CDEmuError(_("String '%s' is not a number") % (arguments.device))

                print(_("Device ID of device %i: %s") % (device, device_id))


########################################################################
#                          Enumerate parsers                           #
########################################################################
class cmd_enum_parsers (object):
    def __init__ (self, subparsers):
        name = "enum-parsers"
        description_msg = _("")
        help_msg = _("enumerates supported parsers")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        # Display supported parsers
        try:
            parsers = proxy.EnumSupportedParsers()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to enumerate supported parsers: %s") % (e))
            return False

        # Print all parsers
        print_header(_("Supported parsers:"))
        for info in parsers:
            print("  %s: %s" % (info[0], info[1]))
            for description, mime_type in info[2]:
                print("    %s: %s" % (mime_type, description))


########################################################################
#                           Enumerate writers                          #
########################################################################
class cmd_enum_writers (object):
    def __init__ (self, subparsers):
        name = "enum-writers"
        description_msg = _("")
        help_msg = _("enumerates supported writers")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        # Display supported writers
        try:
            writers = proxy.EnumSupportedWriters()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to enumerate supported writers: %s") % (e))

        # Print all writers
        print_header(_("Supported writers:"))
        for info in writers:
            print("  %s: %s" % (info[0], info[1]))


########################################################################
#                       Enumerate filter streams                       #
########################################################################
class cmd_enum_filter_streams (object):
    def __init__ (self, subparsers):
        name = "enum-filter-streams"
        description_msg = _("")
        help_msg = _("enumerates supported filter streams")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        # Display supported filter streams
        try:
            filter_streams = proxy.EnumSupportedFilterStreams()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to enumerate supported filter streams: %s") % (e))

        # Print all filter streams
        print_header(_("Supported filter streams:"))
        for info in filter_streams:
            print("  %s: %s" % (info[0], info[1]))
            print(_("    write support: %d") % (info[2]))
            for description, mime_type in info[3]:
                print("    %s: %s" % (mime_type, description))


########################################################################
#                     Enumerate daemon debug masks                     #
########################################################################
class cmd_enum_daemon_debug_masks (object):
    def __init__ (self, subparsers):
        name = "enum-daemon-debug-masks"
        description_msg = _("")
        help_msg = _("enumerates valid daemon debug masks")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        # Print module's debug masks
        try:
            debug_masks = proxy.EnumDaemonDebugMasks()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to enumerate supported daemon debug masks: %s") % (e))

        print_header(_("Supported daemon debug masks:"))
        for debug_mask in debug_masks:
            print("  %-25s: 0x%04X" % (debug_mask[0], debug_mask[1]))


########################################################################
#                     Enumerate library debug masks                    #
########################################################################
class cmd_enum_library_debug_masks (object):
    def __init__ (self, subparsers):
        name = "enum-library-debug-masks"
        description_msg = _("")
        help_msg = _("enumerates valid library debug masks")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        # Print module's debug masks
        try:
            debug_masks = proxy.EnumLibraryDebugMasks()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to enumerate supported library debug masks: %s") % (e))

        print_header(_("Supported library debug masks:"))
        for debug_mask in debug_masks:
            print("  %-25s: 0x%04X" % (debug_mask[0], debug_mask[1]))


########################################################################
#                      Enumerate writer parameters                     #
########################################################################
class cmd_enum_writer_parameters (object):
    def __init__ (self, subparsers):
        name = "enum-writer-parameters"
        description_msg = _("")
        help_msg = _("prints the list of writer's parameters")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

        parser.add_argument("writer", type=str, help=_("writer ID"))

    def __call__ (self, proxy, arguments):
        # Get parameter sheet for the specified writer
        try:
            parameter_sheet = proxy.EnumWriterParameters(arguments.writer)
        except GLib.Error as e:
            raise CDEmuError(_("Failed to get parameter sheet for writer: %s") % (e))

        # Print the sheet
        print_header(_("Writer's parameter sheet:"))

        for parameter in parameter_sheet:
            print("")
            print_header("%s" % (parameter[0]))
            print(_("  Name: %s") % (parameter[1]))
            print(_("  Description: %s") % (parameter[2]))
            print(_("  Type: %s") % (self.parameter_type_name(parameter)))
            print(_("  Default value: %s") % (parameter[3]))

            for enum_value in parameter[4]:
                print(_("  Possible value: %s") % (enum_value))

    def parameter_type_name (self, parameter):
        if len(parameter[4]):
            return _("enum")
        elif isinstance(parameter[3], str):
            return _("string")
        elif isinstance(parameter[3], bool):
            return _("boolean")
        elif isinstance(parameter[3], int):
            return _("integer")
        else:
            return _("unknown")


# This is a stand-alone function because it is shared between
# enum-writer-parameters and create-blank implementations
def fetch_and_parse_writer_parameter_sheet (proxy, writer_id):
    # Enumerate all supported writers
    try:
        parameter_sheet = proxy.EnumWriterParameters(writer_id)
    except GLib.Error as e:
        raise CDEmuError(_("Failed to get parameter sheet for writer: %s") % (e))

    # Find the one we want
    for writer in writers:
        if writer[0] == writer_id:
            break
    else:
        raise CDEmuError(_("Unsupported writer: %s") % (arguments.writer))

    # Parse XML parameter sheet
    try:
        parameters = elementtree.fromstring(writer[2])
    except Exception as e:
        raise CDEmuError(_("Failed to parse parameter sheet: %s") % (e))

    return parameters


########################################################################
#                               Version                                #
########################################################################
class cmd_version (object):
    def __init__ (self, subparsers):
        name = "version"
        description_msg = "Displays daemon and library version information."
        help_msg = _("displays version information")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        # Print version information
        try:
            library_version = proxy.GetLibraryVersion()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to get library version: %s") % (e))

        try:
            daemon_version = proxy.GetDaemonVersion()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to get daemon version: %s") % (e))

        print(_("Library version: %s") % (str(library_version)))
        print(_("Daemon version: %s")  % (str(daemon_version)))


########################################################################
#                              Add device                              #
########################################################################
class cmd_add_device (object):
    def __init__ (self, subparsers):
        name = "add-device"
        description_msg = _("")
        help_msg = _("creates another virtual device")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        try:
            proxy.AddDevice()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to add device: %s") % (e))

        print(_("Device added successfully."))


########################################################################
#                            Remove device                             #
########################################################################
class cmd_remove_device (object):
    def __init__ (self, subparsers):
        name = "remove-device"
        description_msg = _("")
        help_msg = _("removes the last virtual device")

        parser = subparsers.add_parser(name, description=description_msg, help=help_msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
        parser.set_defaults(command_function=self)

    def __call__ (self, proxy, arguments):
        try:
            proxy.RemoveDevice()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to remove device: %s") % (e))

        print(_("Device removed successfully."))


########################################################################
#                         Command-line parser                          #
########################################################################
class CDEmuCommandLineParser (argparse.ArgumentParser):
    def error (self, message):
        self.print_usage(sys.stderr)
        print_error(message)
        sys.exit(2)

class ActionDeviceId (argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
         print("%r %r %r" % (namespace, values, option_string))
         setattr(namespace, self.dest, values)

def create_parser ():
    parser = CDEmuCommandLineParser(description=_("Command-line CDEmu client."), formatter_class=argparse.ArgumentDefaultsHelpFormatter, argument_default=argparse.SUPPRESS)
    parser.add_argument("--bus", metavar="BUS", choices=["system", "session"], help=_("sets D-BUS bus type to use"))
    parser.add_argument("--version", action="version", version="%s %s - (C) 2006-%d Rok Mandeljc" % (app_name, app_version, datetime.date.today().year))

    subparsers = parser.add_subparsers(title=_("valid commands"), metavar="command", help=_("command help"))
    subparsers.required = True

    # Command sub-parsers
    cmd_load(subparsers) # Load
    cmd_create_blank(subparsers) # Create blank
    cmd_unload(subparsers) # Unload
    cmd_display_status(subparsers) # Status
    cmd_add_device(subparsers) # Add device
    cmd_remove_device(subparsers) # Remove device
    cmd_device_mapping(subparsers) # Device mapping
    cmd_daemon_debug_mask(subparsers) # Daemon debug mask
    cmd_library_debug_mask(subparsers) # Library debug mask
    cmd_dpm_emulation(subparsers) # DPM emulation
    cmd_tr_emulation(subparsers) # TR emulation
    cmd_bad_sector_emulation(subparsers) # Bad sector emulation
    cmd_dvd_report_css(subparsers) # DVD report CSS/CPPM
    cmd_device_id(subparsers) # Device ID
    cmd_enum_parsers(subparsers) # Enumerate parsers
    cmd_enum_writers(subparsers) # Enumerate writers
    cmd_enum_filter_streams(subparsers) # Enumerate filter streams
    cmd_enum_daemon_debug_masks(subparsers) # Enumerate daemon debug masks
    cmd_enum_library_debug_masks(subparsers) # Enumerate library debug masks
    cmd_enum_writer_parameters(subparsers) # Enumerate writer parameters
    cmd_version(subparsers) # Version

    return parser


########################################################################
#               CDEmuDaemonProxy: Daemon proxy object                  #
########################################################################
class CDEmuDaemonProxy ():
    _name = 'net.sf.cdemu.CDEmuDaemon'
    _object_path = '/Daemon'

    def __init__ (self, bus_type):
        try:
            if bus_type == "system":
                bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
            elif bus_type == "session":
                bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
            else:
                print_warning(_("Invalid bus parameter '%s', using default!") % (bus_type))
                bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) # Use session bus by default

            self.proxy = Gio.DBusProxy.new_sync(bus, 0, None, "net.sf.cdemu.CDEmuDaemon", "/Daemon", "net.sf.cdemu.CDEmuDaemon", None)
        except GLib.Error as e:
            raise CDEmuError(_("Failed to connect to CDEmu daemon: %s") % (e))

        # Get daemon interface version
        try:
            interface_version = self.GetDaemonInterfaceVersion2()
        except GLib.Error as e:
            raise CDEmuError(_("Failed to acquire daemon interface version (this most likely means your daemon is out-of-date): %s") % (e))

        # Check daemon interface version
        if interface_version[0] != supported_daemon_interface_version[0] or interface_version[1] < supported_daemon_interface_version[1]:
            raise CDEmuError(_("CDEmu daemon interface version %i.%i detected, but version %i.%i is required!") % (interface_version[0], interface_version[1], supported_daemon_interface_version[0], supported_daemon_interface_version[1]))

    def GetDaemonVersion (self):
        return self.proxy.GetDaemonVersion()

    def GetLibraryVersion (self):
        return self.proxy.GetLibraryVersion()

    def GetDaemonInterfaceVersion2 (self):
        return self.proxy.GetDaemonInterfaceVersion2()

    def EnumDaemonDebugMasks (self):
        return self.proxy.EnumDaemonDebugMasks()

    def EnumLibraryDebugMasks (self):
        return self.proxy.EnumLibraryDebugMasks()

    def EnumSupportedParsers (self):
        return self.proxy.EnumSupportedParsers()

    def EnumSupportedWriters (self):
        return self.proxy.EnumSupportedWriters()

    def EnumSupportedFilterStreams (self):
        return self.proxy.EnumSupportedFilterStreams()

    def EnumWriterParameters (self, writer_id):
        return self.proxy.EnumWriterParameters('(s)', writer_id)


    def GetNumberOfDevices (self):
        return self.proxy.GetNumberOfDevices()


    def DeviceGetMapping (self, device_number):
        return self.proxy.DeviceGetMapping('(i)', device_number)

    def DeviceGetStatus (self, device_number):
        return self.proxy.DeviceGetStatus('(i)', device_number)

    def DeviceLoad (self, device_number, filenames, parameters):
        return self.proxy.DeviceLoad('(iasa{sv})', device_number, filenames, parameters)

    def DeviceCreateBlank (self, device_number, filename, parameters):
        return self.proxy.DeviceCreateBlank('(isa{sv})', device_number, filename, parameters)

    def DeviceUnload (self, device_number):
        return self.proxy.DeviceUnload('(i)', device_number)

    def DeviceGetOption (self, device_number, option_name):
        return self.proxy.DeviceGetOption('(is)', device_number, option_name)

    def DeviceSetOption (self, device_number, option_name, option_value):
        return self.proxy.DeviceSetOption('(isv)', device_number, option_name, option_value)


    def AddDevice (self):
        return self.proxy.AddDevice()

    def RemoveDevice (self):
        return self.proxy.RemoveDevice()


########################################################################
#                               Main                                   #
########################################################################
def main ():
    bus_type = "session" # Use session bus as hard-coded default

    # *** Parse config file, if available ***
    # Load options; Try "~/.cdemu-client" first, then try "/etc/cdemu-client.conf" next.
    paths = (os.path.expanduser("~/.cdemu-client"), "/etc/cdemu-client.conf")
    for path in paths:
        if os.path.exists(path):
            break
    else:
        path = None

    if path is not None:
        try:
            config = configparser.ConfigParser()
            config.read(path)

            # Read default bus type
            if config.has_option("defaults", "bus"):
                bus_type = config.get("defaults", "bus")
        except configparser.Error as e:
            # No harm, just print a warning
            print_warning(_("Failed to load configuration from file '%s': %s") % (path, e))

    # *** Parse command-line ***
    arguments = create_parser().parse_args()

    # Do we need to override bus type?
    if hasattr(arguments, "bus"):
        bus_type = arguments.bus

    # *** Connect to daemon ***
    proxy = CDEmuDaemonProxy(bus_type)

    # *** Execute command ***
    arguments.command_function(proxy, arguments)

if __name__ == "__main__":
    try:
        main()
        sys.exit(0)
    except CDEmuError as e:
        print_error(e)
        sys.exit(-1)
