#!/usr/bin/python

## system-config-printer
## Sharing dialog implementation

## Copyright (C) 2001-2003 Red Hat, Inc.
## Copyright (C) 2002-2003 Tim Waugh <twaugh@redhat.com>

## 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., 675 Mass Ave, Cambridge, MA 02139, USA.

import gettext
import gtk
import gtk.glade
import gnome
import gobject
import os
import signal
import string
import re

domain = 'printconf'
from rhpl.translate import _, N_
gtk.glade.bindtextdomain (domain, '/usr/share/locale')

import pyalchemist

def complain (window, msg, type = None):
    """Put up an error message dialog."""
    if not type:
        type = gtk.MESSAGE_ERROR

    d = gtk.MessageDialog (window, 0, type, gtk.BUTTONS_OK, msg)
    d.set_transient_for (window)
    d.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
    d.run ()
    d.destroy ()

class shareQueue:
    """The dialog implementation for sharing an existing queue."""

    def __init__ (self, parent, xml):
        self.parent = parent

        # Widgets.
        self.dialog = xml.get_widget ('sharingDialog')
        self.shared_cb = xml.get_widget ('shared_cb')
        self.allowed_hosts_view = xml.get_widget ('allowed_hosts_view')
        self.hosts_dialog = xml.get_widget ('hostsDialog')
        self.add_button = xml.get_widget ('sharing_add_button')
        self.edit_button = xml.get_widget ('sharing_edit_button')
        self.remove_button = xml.get_widget ('sharing_remove_button')
        self.remove_button.set_sensitive (gtk.FALSE)

        self.all_hosts_rb = xml.get_widget ('all_hosts_rb')
        self.netdev_rb = xml.get_widget ('netdev_rb')
        self.netaddr_rb = xml.get_widget ('netaddr_rb')
        self.ipaddr_rb = xml.get_widget ('ipaddr_rb')
        self.netdev_vbox = xml.get_widget ('netdev_vbox')
        self.netaddr_entry = xml.get_widget ('netaddr_entry')
        self.netmask_entry = xml.get_widget ('netmask_entry')
        self.ipaddr_entry = xml.get_widget ('ipaddr_entry')

        self.sharing_notebook = xml.get_widget ('sharing_notebook')
        self.browsing_cb = xml.get_widget ('browsing_cb')
        self.lpd_cb = xml.get_widget ('lpd_cb')

        # Set up allowed hosts TreeView
        self.allowed_hosts_store = gtk.TreeStore (str, gobject.TYPE_PYOBJECT)
        self.allowed_hosts_view.set_model (self.allowed_hosts_store)
        col = gtk.TreeViewColumn ("", gtk.CellRendererText (), text = 0)
        self.allowed_hosts_view.append_column (col)

        # Prevent signals from causing unintended effects.
        self.dialog_ready = 0

        # Signals.
        self.dialog.connect ('destroy', self.destroy)
        xml.signal_connect ('on_shared_cb_toggled',
                            self.shared_checkbox_toggled)
        xml.signal_connect ('on_allowed_hosts_view_cursor_changed',
                            self.cursor_changed)
        xml.signal_connect ('on_sharing_add_button_clicked',
                            self.add_clicked)
        xml.signal_connect ('on_sharing_edit_button_clicked',
                            self.edit_clicked)
        xml.signal_connect ('on_allowed_hosts_view_row_activated',
                            self.row_activated)
        xml.signal_connect ('on_sharing_remove_button_clicked',
                            self.remove_clicked)
        xml.signal_connect ('on_lpd_cb_toggled',
                            self.lpd_checkbox_toggled)

        xml.signal_connect ('on_all_hosts_rb_toggled',
                            self.all_hosts_radiobutton_toggled)
        xml.signal_connect ('on_netdev_rb_toggled',
                            self.netdev_radiobutton_toggled)
        xml.signal_connect ('on_netaddr_rb_toggled',
                            self.netaddr_radiobutton_toggled)
        xml.signal_connect ('on_netaddr_entry_focus_out_event',
                            self.netaddr_entry_focus_out_event)
        xml.signal_connect ('on_ipaddr_rb_toggled',
                            self.ipaddr_radiobutton_toggled)

        self.ipaddr_re_str = "([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)"
        self.netdev_re = re.compile ("^[a-z]+[0-9]+$")
        self.netdevs_re = re.compile ("^([a-z]+[0-9]+,)+$")

# -------------------------------
# Handle the window being deleted
# -------------------------------
    def destroy (self, dialog):
        """Callback for the window being deleted."""
        dialog.hide ()

#--------------------------
# Sharing properties dialog
#--------------------------
    def shareQueueDialog (self, iter = None):
	"""
	Run a dialog for sharing the queue.

	iter: Iter from queue tree.
	"""

        self.dialog_ready = 0
        self.lpd_warning_issued = 0

        self.queue_tree_iter = iter
        self.sharing_globals = self.get_sharing_globals (self)

        if iter:
            self.name = self.parent.queue_store.get_value (iter, 1)
            self.dialog.set_title (_("Sharing properties - %s") % self.name)
            self.sharing_notebook.set_show_tabs (gtk.TRUE)
            self.sharing_notebook.set_current_page (0)

            # Prepare the widgets
            self.add_button.set_sensitive (gtk.TRUE)
            for button in [self.edit_button, self.remove_button]:
                button.set_sensitive (gtk.FALSE)

            store = self.allowed_hosts_store
            store.clear ()
            self.all_hosts_set = 0
            try:
                del self.sharing
            except:
                pass

            try:
                devs = ""
                queue = self.parent.name_dict[self.name]["queue"]
                self.sharing = queue["sharing"]
                self.shared_cb.set_active (gtk.FALSE)
                for allowed in self.sharing:
                    if self.netdev_re.match (allowed.value):
                        devs += allowed.value + ","
                        continue

                    iter = store.append (None)
                    store.set_value (iter, 1, allowed.value)
                    store.set_value (iter, 0,
                                     self.describe_hosts (allowed.value))
                    self.shared_cb.set_active (gtk.TRUE)

                    if allowed.value == "ALL":
                        self.all_hosts_set = 1

                if devs:
                    iter = store.append (None)
                    store.set_value (iter, 1, devs)
                    store.set_value (iter, 0, self.describe_hosts (devs))
                    self.shared_cb.set_active (gtk.TRUE)
            except:
                # Reset the widgets
                self.shared_cb.set_active (gtk.FALSE)

            self.shared_checkbox_toggled (self.shared_cb)

        else:
            self.dialog.set_title (_("Sharing properties"))
            self.sharing_notebook.set_show_tabs (gtk.FALSE)
            self.sharing_notebook.set_current_page (1)

        browsing = gtk.TRUE
        try:
            if not self.sharing_globals["browsing"].value:
                browsing = gtk.FALSE
        except:
            pass

        self.browsing_cb.set_active (browsing)
        self.init_lpd_checkbutton ()

        # Run the dialog.
        self.dialog.set_transient_for (self.parent.toplevel)
        self.dialog.set_position (gtk.WIN_POS_NONE)
        self.dialog_ready = 1
	backup_ctx = self.parent.conf.queue_edit.dynamic_queue_ctx.copy ()
	while 1:
	    response = self.dialog.run ()

	    # Deal with the consequences.
	    if response == gtk.RESPONSE_OK:
                if self.validate_and_pull ():
		    continue

		break

	    elif (response == gtk.RESPONSE_CANCEL or
                  response == gtk.RESPONSE_DELETE_EVENT):
                self.parent.conf.dynamic_queue_ctx = backup_ctx
		break

	    elif response == gtk.RESPONSE_HELP:
                html = 'printconf-share.html'
		gnome.url_show ("file://%s/%s" %
				(self.parent.conf.conf.printconf_help_dir,
				 html))

        # Deal with non-ADL options.
        try:
            if self.lpd_active != self.lpd_cb.get_active ():
                if self.lpd_cb.get_active ():
                    state = "on"
                else:
                    state = "off"

                os.system ("/sbin/chkconfig cups-lpd %s" % state)
                os.system ("/sbin/service xinetd reload")
        except:
            pass

        self.destroy (self.dialog)
	return response == gtk.RESPONSE_OK

    def validate_and_pull (self):
        """Returns non-zero if there is a problem."""

        if self.queue_tree_iter:
            if not self.shared_cb.get_active ():
                try:
                    self.sharing.unlink ()
                except:
                    pass

                return 0

            try:
                self.sharing.unlink ()
            except:
                pass

            queue = self.parent.name_dict[self.name]["queue"]
            self.sharing = queue.addData (pyalchemist.AdmListType, "sharing")
            self.sharing.anonymous = 1

            i = 1
            store = self.allowed_hosts_store
            for row in store:
                def add_hosts_config (name, value):
                    self.sharing.addData (pyalchemist.AdmStringType,
                                          name).value = value

                iter = store.get_iter (row.path)
                string = store.get_value (iter, 1)
                if self.netdevs_re.match (string):
                    for each in string.split (","):
                        if each:
                            add_hosts_config ("hosts" + str (i), each)
                            i += 1
                else:
                    add_hosts_config ("hosts" + str (i), string)
                    i += 1

        if not self.sharing_globals:
            self.sharing_globals = self.get_sharing_globals (create = 1)

        try:
            self.sharing_globals["browsing"].unlink ()
        except:
            pass

        browsing = self.browsing_cb.get_active ()
        self.sharing_globals.addData (pyalchemist.AdmBoolType,
                                      "browsing").value = browsing

        return 0

    def validate_dotted_quad (self, list):
        """Return non-zero if there is a problem."""
        valid = 1
        for octet in list:
            v = int (octet)
            if v < 0 or v > 255:
                valid = 0
                break

        return not valid

    def describe_hosts (self, string):
        """User-presentable description of ADL hosts string."""
        if string == "ALL":
            return _("All hosts")

        if self.netdevs_re.match (string):
            devs = ""
            count = 0
            for dev in string.split (","):
                if dev:
                    if devs:
                        devs += ", "

                    devs += dev
                    count += 1

            if count == 1:
                return _("Network device %s") % devs

            return _("Network devices %s") % devs

        netaddr_re = re.compile ("^%s/%s$" % (self.ipaddr_re_str,
                                              self.ipaddr_re_str))
        match = netaddr_re.match (string)
        if match and not self.validate_dotted_quad (match.groups ()):
            return _("Network address %s") % string

        ipaddr_re = re.compile ("^%s$" % self.ipaddr_re_str)
        match = ipaddr_re.match (string)
        if match and not self.validate_dotted_quad (match.groups ()):
            return string

        return _("(unknown)")

    def set_button_sensitivity (self):
        edit_remove = gtk.FALSE
        store, iter = self.allowed_hosts_view.get_selection ().get_selected ()
        if iter:
            edit_remove = gtk.TRUE

        self.add_button.set_sensitive (not self.all_hosts_set)
        for button in [self.edit_button, self.remove_button]:
            button.set_sensitive (edit_remove)

    def get_sharing_globals (self, create = 0):
        qe = self.parent.conf.queue_edit
        try:
            return qe.dynamic_queue_ctx.data['/printconf/sharing_globals']
        except:
            pass

        try:
            return qe.static_queue_ctx.data['/printconf/sharing_globals']
        except:
            pass

        if create:
            toplevel = qe.dynamic_queue_ctx.data['/printconf']
            return toplevel.addData (pyalchemist.AdmListType,
                                     "sharing_globals")

        return None

    def init_lpd_checkbutton (self):
        cb = self.lpd_cb
        signal.signal (signal.SIGCHLD, signal.SIG_DFL)
        f = os.popen ('/sbin/chkconfig --list cups-lpd 2>/dev/null')
        ls = f.readlines ()
        try:
            state = ls[0].split ('\t')[1].strip ()
            self.lpd_active = state == "on"
            cb.set_active (self.lpd_active)
            cb.set_sensitive (gtk.TRUE)
        except:
            cb.set_active (gtk.FALSE)
            cb.set_sensitive (gtk.FALSE)

#---------------------------------------
# Handlers for sharing properties dialog
#---------------------------------------
    def shared_checkbox_toggled (self, checkbox):
        active = checkbox.get_active ()
        self.allowed_hosts_view.set_sensitive (active)

        if active and len (self.allowed_hosts_store) == 0:
            store = self.allowed_hosts_store
            iter = store.append (None)
            all = "ALL"
            store.set_value (iter, 1, all)
            store.set_value (iter, 0, self.describe_hosts (all))
            self.all_hosts_set = 1
            sel = self.allowed_hosts_view.get_selection ()
            sel.select_iter (iter)
            self.set_button_sensitivity ()
        else:
            if active:
                self.set_button_sensitivity ()
            else:
                for button in [self.add_button, self.edit_button,
                               self.remove_button]:
                    button.set_sensitive (gtk.FALSE)

    def add_clicked (self, button):
        self.run_hosts_dialog (_("Add allowed hosts - %s") % self.name)

    def edit_clicked (self, button = None):
        store, iter = self.allowed_hosts_view.get_selection ().get_selected ()
        self.run_hosts_dialog (_("Edit allowed hosts - %s") % self.name, iter)

    def remove_clicked (self, button):
        store, iter = self.allowed_hosts_view.get_selection ().get_selected ()
        string = store.get_value (iter, 1)
        store.remove (iter)
        if string == "ALL":
            self.all_hosts_set = gtk.FALSE

        self.set_button_sensitivity ()

    def cursor_changed (self, view):
        self.set_button_sensitivity ()

    def row_activated (self, view):
        self.edit_clicked ()

    def lpd_checkbox_toggled (self, checkbox):
        if self.lpd_warning_issued:
            return

        if not checkbox.get_active ():
            return

        if not self.dialog_ready:
            return

        self.lpd_warning_issued = 1
        complain (self.dialog,
                  _("LPD connections will be accepted\n"
                    "on all interfaces, and jobs for\n"
                    "all queues will be accepted."),
                  gtk.MESSAGE_WARNING)

#---------------------
# Allowed hosts dialog
#---------------------
    def run_hosts_dialog (self, title, iter = None):
	"""
	Run a dialog for adding/editing allowed hosts.

	title: Title to give the dialog.
	"""

        self.hosts_dialog.set_title (title)

        # Reset the widgets.
        for child in self.netdev_vbox.get_children ():
            self.netdev_vbox.remove (child)

        for line in file ("/proc/net/dev").readlines ():
            if line.find ("|") != -1:
                continue

            end = line.find (":")
            if end == -1:
                continue

            dev = line[:end].strip ()
            if dev == "lo":
                continue

            cb = gtk.CheckButton (dev)
            self.netdev_vbox.add (cb)
            self.netdev_vbox.show_all ()

        self.netaddr_entry.set_text ('')
        self.netmask_entry.set_text ('')
        self.ipaddr_entry.set_text ('')
        self.all_hosts_rb.set_active (gtk.TRUE)
        self.all_hosts_radiobutton_toggled (self.all_hosts_rb)

        if iter:
            string = self.allowed_hosts_store.get_value (iter, 1)
            if self.netdevs_re.match (string):
                self.netdev_rb.set_active (gtk.TRUE)
                self.netdev_radiobutton_toggled (self.netdev_rb)
                done = 0
                for dev in self.netdev_vbox.get_children ():
                    for each in string.split (","):
                        if each:
                            if dev.get_label () == each:
                                dev.set_active (gtk.TRUE)
                                done = 1

                if not done:
                    for each in string.split (","):
                        if each:
                            dev = gtk.CheckButton (each)
                            self.netdev_vbox.add (dev)
                            dev.set_active (gtk.TRUE)

            else:
                netaddr_re = re.compile ("^%s/%s$" % (self.ipaddr_re_str,
                                                      self.ipaddr_re_str))
                match = netaddr_re.match (string)
                if match:
                    self.netaddr_rb.set_active (gtk.TRUE)
                    self.netaddr_radiobutton_toggled (self.netaddr_rb)
                    quad = match.groups ()
                    self.netaddr_entry.set_text ("%s.%s.%s.%s" % quad[:4])
                    self.netmask_entry.set_text ("%s.%s.%s.%s" % quad[4:])

                else:
                    ipaddr_re = re.compile ("^%s$" % self.ipaddr_re_str)
                    if ipaddr_re.match (string):
                        self.ipaddr_rb.set_active (gtk.TRUE)
                        self.ipaddr_radiobutton_toggled (self.ipaddr_rb)
                        self.ipaddr_entry.set_text (string)
                
        # Run the dialog.
        self.hosts_dialog.set_transient_for (self.dialog)
        self.hosts_dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
        while 1:
            response = self.hosts_dialog.run ()

            # Deal with the consequences.
            if response == gtk.RESPONSE_OK:
                if self.validate_hosts_and_pull ():
                    continue

                if iter and not self.all_hosts_set:
                    self.allowed_hosts_store.remove (iter)

                break

            elif (response == gtk.RESPONSE_CANCEL or
                  response == gtk.RESPONSE_DELETE_EVENT):
                break

            elif response == gtk.RESPONSE_HELP:
                gnome.url_show ("file://%s/printconf-share.html#FIG-PRINTING-SHARE-HOSTS" %
                                self.parent.conf.conf.printconf_help_dir)

        self.destroy (self.hosts_dialog)

        # Merge network devices.
        store = self.allowed_hosts_store
        netdevs_iter = None
        devs = []
        for row in store:
            iter = store.get_iter (row.path)
            string = store.get_value (iter, 1)
            if self.netdevs_re.match (string):
                if netdevs_iter:
                    for dev in string.split (","):
                        if dev:
                            try:
                                devs.index (dev)
                            except:
                                devs.append (dev)

                    store.remove (iter)
                else:
                    netdevs_iter = iter
                    devs = string.split (",")
                    del devs[len (devs) - 1]

        if netdevs_iter:
            devstr = ""
            for dev in devs:
                devstr += dev + ","

            store.set_value (netdevs_iter, 1, devstr)
            store.set_value (netdevs_iter, 0, self.describe_hosts (devstr))
            self.allowed_hosts_view.get_selection ().select_iter (netdevs_iter)

        self.set_button_sensitivity ()

    def get_hosts_string (self):
        """Form the ADL string representing the allowed hosts."""
        if self.all_hosts_rb.get_active ():
            return "ALL"

        if self.netdev_rb.get_active ():
            string = ""
            for dev in self.netdev_vbox.get_children ():
                if dev.get_active ():
                    string += dev.get_label () + ","

            return string

        if self.netaddr_rb.get_active ():
            addr = self.netaddr_entry.get_text ()
            mask = self.netmask_entry.get_text ()
            ipaddr_re = re.compile ("^%s$" % self.ipaddr_re_str)
            match = ipaddr_re.match (addr)
            if not match or self.validate_dotted_quad (match.groups ()):
                complain (self.hosts_dialog, _("Invalid network address"))
                self.netaddr_entry.grab_focus ()
                return None

            match = ipaddr_re.match (mask)
            if not match:
                try:
                    cidr = int (mask)
                    if cidr <= 32:
                        def n_high_bits (n):
                            q = 0
                            for b in range (1, 9):
                                if n < b:
                                    break

                                q += pow (2, 8 - b)

                            return q

                        quad = [0, 0, 0, 0]
                        for i in range (4):
                            if cidr >= 8:
                                quad[i] = 255
                            else:
                                quad[i] = n_high_bits (cidr)

                            cidr -= 8
                            if cidr < 0:
                                break

                    mask = "%d.%d.%d.%d" % tuple (quad)
                    self.netmask_entry.set_text (mask)
                    match = ipaddr_re.match (mask)
                except:
                    pass

            if not match or self.validate_dotted_quad (match.groups ()):
                complain (self.hosts_dialog, _("Invalid netmask"))
                self.netmask_entry.grab_focus ()
                return None

            return "%s/%s" % (addr, mask)

        if self.ipaddr_rb.get_active ():
            ipaddr = self.ipaddr_entry.get_text ()
            ipaddr_re = re.compile ("^%s$" % self.ipaddr_re_str)
            match = ipaddr_re.match (ipaddr)
            if not match or self.validate_dotted_quad (match.groups ()):
                complain (self.hosts_dialog, _("Invalid IP address"))
                self.ipaddr_entry.grab_focus ()
                return None

            return ipaddr

        return None

    def validate_hosts_and_pull (self):
        """Returns non-zero if there was a problem."""
        string = self.get_hosts_string ()
        if not string:
            return 1

        store = self.allowed_hosts_store
        if string == "ALL":
            self.all_hosts_set = 1
            store.clear ()
        else:
            self.all_hosts_set = 0

        iter = store.append (None)
        store.set_value (iter, 1, string)
        store.set_value (iter, 0, self.describe_hosts (string))
        self.allowed_hosts_view.get_selection ().select_iter (iter)
        return 0

#----------------------------------
# Handlers for allowed hosts dialog
#----------------------------------
    def all_hosts_radiobutton_toggled (self, rb):
        for each in [self.netaddr_entry,
                     self.netmask_entry,
                     self.ipaddr_entry]:
            each.set_sensitive (gtk.FALSE)

        for each in self.netdev_vbox.get_children ():
            each.set_sensitive (gtk.FALSE)

    def netdev_radiobutton_toggled (self, rb):
        for each in [self.netaddr_entry,
                     self.netmask_entry,
                     self.ipaddr_entry]:
            each.set_sensitive (gtk.FALSE)

        for each in self.netdev_vbox.get_children ():
            each.set_sensitive (gtk.TRUE)

    def netaddr_radiobutton_toggled (self, rb):
        self.netaddr_entry.set_sensitive (gtk.TRUE)
        self.netmask_entry.set_sensitive (gtk.TRUE)
        self.ipaddr_entry.set_sensitive (gtk.FALSE)

        for each in self.netdev_vbox.get_children ():
            each.set_sensitive (gtk.FALSE)

        self.netaddr_entry.grab_focus ()

    def netaddr_entry_focus_out_event (self, *args):
        entry = self.netmask_entry
        if entry.get_text () == '':
            netaddr = self.netaddr_entry.get_text ()
            ipaddr_re = re.compile ("^%s$" % self.ipaddr_re_str)
            match = ipaddr_re.match (netaddr)
            if match:
                first = match.groups ()[0]
                if first < 128:
                    netmask = "255.0.0.0"
                elif first < 192:
                    netmask = "255.255.0.0"
                else:
                    netmask = "255.255.255.0"

                entry.set_text (netmask)

    def ipaddr_radiobutton_toggled (self, rb):
        self.netaddr_entry.set_sensitive (gtk.FALSE)
        self.netmask_entry.set_sensitive (gtk.FALSE)
        self.ipaddr_entry.set_sensitive (gtk.TRUE)

        for each in self.netdev_vbox.get_children ():
            each.set_sensitive (gtk.FALSE)

        self.ipaddr_entry.grab_focus ()

# Local Variables:
# py-indent-offset: 4
# End:
