Back to index

system-config-printer  1.3.9+20120706
serversettings.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 ## system-config-printer
00004 
00005 ## Copyright (C) 2008, 2009, 2010, 2011 Red Hat, Inc.
00006 ## Authors:
00007 ##  Tim Waugh <twaugh@redhat.com>
00008 
00009 ## This program is free software; you can redistribute it and/or modify
00010 ## it under the terms of the GNU General Public License as published by
00011 ## the Free Software Foundation; either version 2 of the License, or
00012 ## (at your option) any later version.
00013 
00014 ## This program is distributed in the hope that it will be useful,
00015 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017 ## GNU General Public License for more details.
00018 
00019 ## You should have received a copy of the GNU General Public License
00020 ## along with this program; if not, write to the Free Software
00021 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00022 
00023 import config
00024 from gettext import gettext as _
00025 import cups
00026 import dbus
00027 import gobject
00028 import gtk
00029 import os
00030 import socket
00031 import tempfile
00032 import time
00033 
00034 import authconn
00035 from debug import *
00036 from errordialogs import *
00037 import firewall
00038 from gui import GtkGUI
00039 
00040 try:
00041     try_CUPS_SERVER_REMOTE_ANY = cups.CUPS_SERVER_REMOTE_ANY
00042 except AttributeError:
00043     # cups module was compiled with CUPS < 1.3
00044     try_CUPS_SERVER_REMOTE_ANY = "_remote_any"
00045 
00046 # Set up "Problems?" link button
00047 class _UnobtrusiveButton(gtk.Button):
00048     def __init__ (self, **args):
00049         gtk.Button.__init__ (self, **args)
00050         self.set_relief (gtk.RELIEF_NONE)
00051         label = self.get_child ()
00052         text = label.get_text ()
00053         label.set_use_markup (True)
00054         label.set_markup ('<span size="small" ' +
00055                           'underline="single" ' +
00056                           'color="#0000ee">%s</span>' % text)
00057 
00058 class ServerSettings(GtkGUI):
00059 
00060     __gsignals__ = {
00061         'settings-applied': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
00062         'dialog-canceled': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
00063         'problems-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
00064         }
00065 
00066     RESOURCE="/admin/conf/cupsd.conf"
00067 
00068     def __init__ (self, host=None, encryption=None, parent=None):
00069         gobject.GObject.__init__ (self)
00070         self.cupsconn = authconn.Connection (host=host, encryption=encryption)
00071         self._host = host
00072         self._parent = parent
00073         self.getWidgets({"ServerSettingsDialog":
00074                              ["ServerSettingsDialog",
00075                               "chkServerBrowse",
00076                               "chkServerShare",
00077                               "chkServerShareAny",
00078                               "chkServerRemoteAdmin",
00079                               "chkServerAllowCancelAll",
00080                               "chkServerLogDebug",
00081                               "hboxServerBrowse",
00082                               "rbPreserveJobFiles",
00083                               "rbPreserveJobHistory",
00084                               "rbPreserveJobNone",
00085                               "tvBrowseServers",
00086                               "frameBrowseServers",
00087                               "btAdvServerAdd",
00088                               "btAdvServerRemove"]},
00089 
00090                         domain=config.PACKAGE)
00091 
00092         problems = _UnobtrusiveButton (label=_("Problems?"))
00093         self.hboxServerBrowse.pack_end (problems, False, False, 0)
00094         problems.connect ('clicked', self.problems_clicked)
00095         problems.show ()
00096 
00097         self.ServerSettingsDialog.connect ('response', self.on_response)
00098 
00099         # Signal handler IDs.
00100         self.handler_ids = {}
00101 
00102         self.dialog = self.ServerSettingsDialog
00103         self.browse_treeview = self.tvBrowseServers
00104         self.add = self.btAdvServerAdd
00105         self.remove = self.btAdvServerRemove
00106 
00107         selection = self.browse_treeview.get_selection ()
00108         selection.set_mode (gtk.SELECTION_MULTIPLE)
00109         self._connect (selection, 'changed', self.on_treeview_selection_changed)
00110 
00111         for column in self.browse_treeview.get_columns():
00112             self.browse_treeview.remove_column(column)
00113         col = gtk.TreeViewColumn ('', gtk.CellRendererText (), text=0)
00114         self.browse_treeview.append_column (col)
00115 
00116         self._fillAdvanced ()
00117         self._fillBasic ()
00118 
00119         if parent:
00120             self.dialog.set_transient_for (parent)
00121 
00122         self.dialog.show ()
00123 
00124     def get_dialog (self):
00125         return self.dialog
00126 
00127     def problems_clicked (self, button):
00128         self.emit ('problems-clicked')
00129 
00130     def _fillAdvanced(self):
00131         # Fetch cupsd.conf
00132         f = tempfile.TemporaryFile ()
00133         try:
00134             self.cupsconn.getFile (self.RESOURCE, file=f)
00135         except cups.HTTPError, (s,):
00136             show_HTTP_Error (s, self._parent)
00137             raise
00138 
00139         def parse_yesno (line):
00140             arg1 = line.split (' ')[1].strip ()
00141             if arg1 in ['true', 'on', 'enabled', 'yes']:
00142                 return True
00143             if arg1 in ['false', 'off', 'disabled', 'no', '0']:
00144                 return False
00145             try:
00146                 if int (arg1) != 0:
00147                     return True
00148             except:
00149                 pass
00150             raise RuntimeError
00151 
00152         preserve_job_history = True
00153         preserve_job_files = False
00154         browsing = True
00155         self.browse_poll = []
00156         f.seek (0)
00157         for line in f.readlines ():
00158             l = line.lower ().strip ()
00159             if l.startswith ("preservejobhistory "):
00160                 try:
00161                     preserve_job_history = parse_yesno (l)
00162                 except:
00163                     pass
00164             elif l.startswith ("preservejobfiles "):
00165                 try:
00166                     preserve_job_files = parse_yesno (l)
00167                 except:
00168                     pass
00169             elif l.startswith ("browsing "):
00170                 try:
00171                     browsing = parse_yesno (l)
00172                 except:
00173                     pass
00174             elif l.startswith ("browsepoll "):
00175                 self.browse_poll.append (line[len ("browsepoll "):].strip ())
00176 
00177         self.frameBrowseServers.set_sensitive (browsing)
00178 
00179         if preserve_job_files:
00180             self.rbPreserveJobFiles.set_active (True)
00181         elif preserve_job_history:
00182             self.rbPreserveJobHistory.set_active (True)
00183         else:
00184             self.rbPreserveJobNone.set_active (True)
00185 
00186         self.preserve_job_history = preserve_job_history
00187         self.preserve_job_files = preserve_job_files
00188 
00189         model = gtk.ListStore (gobject.TYPE_STRING)
00190         self.browse_treeview.set_model (model)
00191         for server in self.browse_poll:
00192             model.append (row=[server])
00193 
00194     def _fillBasic(self):
00195         self.changed = set()
00196         self.cupsconn._begin_operation (_("fetching server settings"))
00197         try:
00198             self.server_settings = self.cupsconn.adminGetServerSettings()
00199         except cups.IPPError, (e, m):
00200             show_IPP_Error(e, m, self._parent)
00201             self.cupsconn._end_operation ()
00202             raise
00203 
00204         self.cupsconn._end_operation ()
00205 
00206         for widget, setting in [
00207             (self.chkServerBrowse, cups.CUPS_SERVER_REMOTE_PRINTERS),
00208             (self.chkServerShare, cups.CUPS_SERVER_SHARE_PRINTERS),
00209             (self.chkServerShareAny, try_CUPS_SERVER_REMOTE_ANY),
00210             (self.chkServerRemoteAdmin, cups.CUPS_SERVER_REMOTE_ADMIN),
00211             (self.chkServerAllowCancelAll, cups.CUPS_SERVER_USER_CANCEL_ANY),
00212             (self.chkServerLogDebug, cups.CUPS_SERVER_DEBUG_LOGGING),]:
00213             widget.set_data("setting", setting)
00214             if self.server_settings.has_key(setting):
00215                 widget.set_active(int(self.server_settings[setting]))
00216                 widget.set_sensitive(True)
00217             else:
00218                 widget.set_active(False)
00219                 widget.set_sensitive(False)
00220 
00221         try:
00222             flag = cups.CUPS_SERVER_SHARE_PRINTERS
00223             publishing = int (self.server_settings[flag])
00224             self.server_is_publishing = publishing
00225         except AttributeError:
00226             pass
00227 
00228         # Set sensitivity of 'Allow printing from the Internet'.
00229         self.on_server_changed (self.chkServerShare) # (any will do here)
00230 
00231     def on_server_changed(self, widget):
00232         debugprint ("on_server_changed: %s" % widget)
00233         setting = widget.get_data("setting")
00234         if self.server_settings.has_key (setting):
00235             if str(int(widget.get_active())) == self.server_settings[setting]:
00236                 self.changed.discard(widget)
00237             else:
00238                 self.changed.add(widget)
00239 
00240         sharing = self.chkServerShare.get_active ()
00241         self.chkServerShareAny.set_sensitive (
00242             sharing and self.server_settings.has_key(try_CUPS_SERVER_REMOTE_ANY))
00243 
00244     def _connect (self, widget, signal, handler, reason=None):
00245         id = widget.connect (signal, handler)
00246         if not self.handler_ids.has_key (reason):
00247             self.handler_ids[reason] = []
00248         self.handler_ids[reason].append ((widget, id))
00249 
00250     def _disconnect (self, reason=None):
00251         if self.handler_ids.has_key (reason):
00252             for (widget, id) in self.handler_ids[reason]:
00253                 widget.disconnect (id)
00254             del self.handler_ids[reason]
00255 
00256     def on_treeview_selection_changed (self, selection):
00257         self.remove.set_sensitive (selection.count_selected_rows () != 0)
00258 
00259     def on_add_clicked (self, button):
00260         model = self.browse_treeview.get_model ()
00261         iter = model.insert (0, row=[_("Enter hostname")])
00262         button.set_sensitive (False)
00263         col = self.browse_treeview.get_columns ()[0]
00264         cell = col.get_cell_renderers ()[0]
00265         cell.set_property ('editable', True)
00266         self.browse_treeview.set_cursor ((0,), col, start_editing=True)
00267         self._connect (cell, 'edited', self.on_browse_poll_edited, 'edit')
00268         self._connect (cell, 'editing-canceled',
00269                        self.on_browse_poll_edit_cancel, 'edit')
00270 
00271     def on_browse_poll_edited (self, cell, path, newvalue):
00272         model = self.browse_treeview.get_model ()
00273         iter = model.get_iter (path)
00274         model.set_value (iter, 0, newvalue)
00275         cell.stop_editing (canceled=False)
00276         cell.set_property ('editable', False)
00277         self.add.set_sensitive (True)
00278         self._disconnect ('edit')
00279 
00280         valid = True
00281         # Check that it's a valid IP address or hostname.
00282         # First, is it an IP address?
00283         try:
00284             socket.getaddrinfo (newvalue, '0', socket.AF_UNSPEC, 0, 0,
00285                                 socket.AI_NUMERICHOST)
00286         except socket.gaierror:
00287             # No.  Perhaps it's a hostname.
00288             labels = newvalue.split (".")
00289             seen_alpha = False
00290             for label in labels:
00291                 if (label[0] == '-' or
00292                     label.endswith ('-')):
00293                     valid = False
00294                     break
00295                 for char in label:
00296                     if not seen_alpha:
00297                         if char.isalpha ():
00298                             seen_alpha = True
00299 
00300                     if not (char.isalpha () or
00301                             char.isdigit () or
00302                             char == '-'):
00303                         valid = False
00304                         break
00305 
00306                 if not valid:
00307                     break
00308 
00309             if valid and not seen_alpha:
00310                 valid = False
00311 
00312         if valid:
00313             count = 0
00314             i = model.get_iter_first ()
00315             while i:
00316                 if model.get_value (i, 0) == newvalue:
00317                     count += 1
00318                     if count == 2:
00319                         valid = False
00320                         selection = self.browse_treeview.get_selection ()
00321                         selection.select_iter (i)
00322                         break
00323                 i = model.iter_next (i)
00324         else:
00325             model.remove (iter)
00326 
00327     def on_browse_poll_edit_cancel (self, cell):
00328         cell.stop_editing (canceled=True)
00329         cell.set_property ('editable', False)
00330         model = self.browse_treeview.get_model ()
00331         iter = model.get_iter ((0,))
00332         model.remove (iter)
00333         self.add.set_sensitive (True)
00334         self.remove.set_sensitive (False)
00335         self._disconnect ('edit')
00336 
00337     def on_remove_clicked (self, button):
00338         model = self.browse_treeview.get_model ()
00339         selection = self.browse_treeview.get_selection ()
00340         rows = selection.get_selected_rows ()
00341         refs = map (lambda path: gtk.TreeRowReference (model, path),
00342                     rows[1])
00343         for ref in refs:
00344             path = ref.get_path ()
00345             iter = model.get_iter (path)
00346             model.remove (iter)
00347 
00348     def on_response (self, dialog, response):
00349         if (response == gtk.RESPONSE_CANCEL or
00350             response != gtk.RESPONSE_OK):
00351             self._disconnect ()
00352             self.dialog.hide ()
00353             self.emit ('dialog-canceled')
00354             del self
00355             return
00356 
00357         self.saveBasic ()
00358         self.saveAdvanced ()
00359 
00360     def _reconnect (self):
00361         # Now reconnect, in case the server needed to reload.
00362         try:
00363             attempt = 1
00364             while attempt <= 5:
00365                 try:
00366                     self.cupsconn._connect ()
00367                     break
00368                 except RuntimeError:
00369                     # Connection failed.
00370                     time.sleep (1)
00371                     attempt += 1
00372         except AttributeError:
00373             # _connect method is part of the authconn.Connection
00374             # interface, so don't fail if that method doesn't exist.
00375             pass
00376 
00377     def saveAdvanced (self):
00378         # See if there are changes.
00379         preserve_job_files = self.rbPreserveJobFiles.get_active ()
00380         preserve_job_history = (preserve_job_files or
00381                                 self.rbPreserveJobHistory.get_active ())
00382         model = self.browse_treeview.get_model ()
00383         browse_poll = []
00384         iter = model.get_iter_first ()
00385         while iter:
00386             browse_poll.append (model.get_value (iter, 0))
00387             iter = model.iter_next (iter)
00388 
00389         if (set (browse_poll) == set (self.browse_poll) and
00390             preserve_job_files == self.preserve_job_files and
00391             preserve_job_history == self.preserve_job_history):
00392             self._disconnect ()
00393             self.dialog.hide ()
00394             self.emit ('settings-applied')
00395             del self
00396             return
00397 
00398         # Fetch cupsd.conf afresh
00399         f = tempfile.TemporaryFile ()
00400         try:
00401             self.cupsconn.getFile (self.RESOURCE, file=f)
00402         except cups.HTTPError, (s,):
00403             show_HTTP_Error (s, self.dialog)
00404             return
00405 
00406         job_history_line = job_files_line = browsepoll_lines = ""
00407 
00408         # Default is to preserve job history
00409         if not preserve_job_history:
00410             job_history_line = "PreserveJobHistory No\n"
00411 
00412         # Default is not to preserve job files.
00413         if preserve_job_files:
00414             job_files_line = "PreserveJobFiles Yes\n"
00415 
00416         for server in browse_poll:
00417             browsepoll_lines += "BrowsePoll %s\n" % server
00418 
00419         f.seek (0)
00420         conf = tempfile.TemporaryFile ()
00421         wrote_preserve_history = wrote_preserve_files = False
00422         wrote_browsepoll = False
00423         has_browsepoll = False
00424         lines = f.readlines ()
00425         for line in lines:
00426             l = line.lower ().strip ()
00427             if l.startswith ("browsepoll "):
00428                 has_browsepoll = True
00429                 break
00430 
00431         for line in lines:
00432             l = line.lower ().strip ()
00433             if l.startswith ("preservejobhistory "):
00434                 if wrote_preserve_history:
00435                     # Don't write out another line with this keyword.
00436                     continue
00437                 # Alter this line before writing it out.
00438                 line = job_history_line
00439                 wrote_preserve_history = True
00440             elif l.startswith ("preservejobfiles "):
00441                 if wrote_preserve_files:
00442                     # Don't write out another line with this keyword.
00443                     continue
00444                 # Alter this line before writing it out.
00445                 line = job_files_line
00446                 wrote_preserve_files = True
00447             elif (has_browsepoll and
00448                   l.startswith ("browsepoll ")):
00449                 if wrote_browsepoll:
00450                     # Ignore extra BrowsePoll lines.
00451                     continue
00452                 # Write new BrowsePoll section.
00453                 conf.write (browsepoll_lines)
00454                 wrote_browsepoll = True
00455                 # Don't write out the original BrowsePoll line.
00456                 continue
00457             elif (not has_browsepoll and
00458                   l.startswith ("browsing ")):
00459                 if not wrote_browsepoll:
00460                     # Write original Browsing line.
00461                     conf.write (line)
00462                     # Write new BrowsePoll section.
00463                     conf.write (browsepoll_lines)
00464                     wrote_browsepoll = True
00465                     continue
00466 
00467             conf.write (line)
00468 
00469         if not wrote_preserve_history:
00470             conf.write (job_history_line)
00471         if not wrote_preserve_files:
00472             conf.write (job_files_line)
00473         if not wrote_browsepoll:
00474             conf.write (browsepoll_lines)
00475 
00476         conf.flush ()
00477         fd = conf.fileno ()
00478         os.lseek (fd, 0, os.SEEK_SET)
00479         try:
00480             self.cupsconn.putFile ("/admin/conf/cupsd.conf", fd=fd)
00481         except cups.HTTPError, (s,):
00482             show_HTTP_Error (s, self.dialog)
00483             return
00484 
00485         # Give the server a chance to process our request.
00486         time.sleep (1)
00487 
00488         self._reconnect ()
00489 
00490         self._disconnect ()
00491         self.emit ('settings-applied')
00492         self.dialog.hide ()
00493         del self
00494 
00495     def saveBasic (self):
00496         setting_dict = dict()
00497         for widget, setting in [
00498             (self.chkServerBrowse, cups.CUPS_SERVER_REMOTE_PRINTERS),
00499             (self.chkServerShare, cups.CUPS_SERVER_SHARE_PRINTERS),
00500             (self.chkServerShareAny, try_CUPS_SERVER_REMOTE_ANY),
00501             (self.chkServerRemoteAdmin, cups.CUPS_SERVER_REMOTE_ADMIN),
00502             (self.chkServerAllowCancelAll, cups.CUPS_SERVER_USER_CANCEL_ANY),
00503             (self.chkServerLogDebug, cups.CUPS_SERVER_DEBUG_LOGGING),]:
00504             if not self.server_settings.has_key(setting): continue
00505             setting_dict[setting] = str(int(widget.get_active()))
00506         self.cupsconn._begin_operation (_("modifying server settings"))
00507         try:
00508             self.cupsconn.adminSetServerSettings(setting_dict)
00509         except cups.IPPError, (e, m):
00510             show_IPP_Error(e, m, self.dialog)
00511             self.cupsconn._end_operation ()
00512             return True
00513         except RuntimeError, s:
00514             show_IPP_Error(None, s, self.dialog)
00515             self.cupsconn._end_operation ()
00516             return True
00517         self.cupsconn._end_operation ()
00518         self.changed = set()
00519 
00520         old_setting = self.server_settings.get (cups.CUPS_SERVER_SHARE_PRINTERS,
00521                                                 '0')
00522         new_setting = setting_dict.get (cups.CUPS_SERVER_SHARE_PRINTERS, '0')
00523         if (old_setting == '0' and new_setting != '0'):
00524             # We have just enabled print queue sharing.
00525             # Let's see if the firewall will allow IPP TCP packets in.
00526             try:
00527                 if (self._host == 'localhost' or
00528                     self._host[0] == '/'):
00529                     f = firewall.Firewall ()
00530                     allowed = f.check_ipp_server_allowed ()
00531                 else:
00532                     # This is a remote server.  Nothing we can do
00533                     # about the firewall there.
00534                     allowed = True
00535 
00536                 if not allowed:
00537                     dialog = gtk.MessageDialog (self.ServerSettingsDialog,
00538                                                 gtk.DIALOG_MODAL |
00539                                                 gtk.DIALOG_DESTROY_WITH_PARENT,
00540                                                 gtk.MESSAGE_QUESTION,
00541                                                 gtk.BUTTONS_NONE,
00542                                                 _("Adjust Firewall"))
00543                     dialog.format_secondary_text (_("Adjust the firewall now "
00544                                                     "to allow all incoming IPP "
00545                                                     "connections?"))
00546                     dialog.add_buttons (gtk.STOCK_CANCEL, gtk.RESPONSE_NO,
00547                                         _("Adjust Firewall"), gtk.RESPONSE_YES)
00548                     response = dialog.run ()
00549                     dialog.destroy ()
00550 
00551                     if response == gtk.RESPONSE_YES:
00552                         f.add_rule (f.ALLOW_IPP_SERVER)
00553                         f.write ()
00554             except (dbus.DBusException, Exception):
00555                 nonfatalException ()
00556 
00557         time.sleep(1) # give the server a chance to process our request
00558 
00559         # Now reconnect, in case the server needed to reload.
00560         self._reconnect ()
00561 
00562 gobject.type_register (ServerSettings)
00563 
00564 if __name__ == '__main__':
00565     os.environ['SYSTEM_CONFIG_PRINTER_UI'] = 'ui'
00566     loop = gobject.MainLoop ()
00567 
00568     def quit (*args):
00569         loop.quit ()
00570 
00571     def problems (obj):
00572         print "%s: problems" % obj
00573 
00574     set_debugging (True)
00575     s = ServerSettings ()
00576     s.connect ('dialog-canceled', quit)
00577     s.connect ('settings-applied', quit)
00578     s.connect ('problems-clicked', problems)
00579     loop.run ()