2 # -*- coding: utf-8 -*-
4 # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 __title__ = "Services and Status System Tray dBus Child/Parent Process"
26 __doc__ = "Provides persistent data and event services to HPLIP client applications. Required to be running for PC send fax, optional in all other cases."
40 from cPickle import loads, HIGHEST_PROTOCOL
44 from base.codes import *
45 from base import utils, device, status, models, module
46 from installer import core_install
50 from dbus import lowlevel, SystemBus, SessionBus
52 from dbus.mainloop.glib import DBusGMainLoop
53 from gobject import MainLoop, timeout_add, threads_init, io_add_watch, IO_IN
56 log.error("dbus failed to load (python-dbus ver. 0.80+ required). Exiting...")
61 # Ignore: .../dbus/connection.py:242: DeprecationWarning: object.__init__() takes no parameters
62 # (occurring on Python 2.6/dBus 0.83/Ubuntu 9.04)
63 warnings.simplefilter("ignore", DeprecationWarning)
68 dbus_loop, main_loop = None, None
71 w1, w2, r3 = None, None, None
72 devices = {} # { 'device_uri' : DeviceCache, ... }
75 # ***********************************************************************************
79 # ***********************************************************************************
81 class DeviceCache(object):
82 def __init__(self, model=''):
83 self.history = utils.RingBuffer(prop.history_size) # circular buffer of device.Event
84 self.model = models.normalizeModelName(model)
85 self.cache = {} # variable name : value
86 self.faxes = {} # (username, jobid): FaxEvent
87 self.dq = {} # last device query results
89 self.backoff_counter = 0 # polling backoff: 0 = none, x = backed off by x intervals
90 self.backoff_countdown = 0
91 self.polling = False # indicates whether its in the device polling list
94 # dbus interface on session bus
95 class StatusService(dbus.service.Object):
96 def __init__(self, name, object_path):
97 dbus.service.Object.__init__(self, name, object_path)
100 @dbus.service.method('com.hplip.StatusService', in_signature='s', out_signature='sa(ssisisd)')
101 def GetHistory(self, device_uri):
102 log.debug("GetHistory('%s')" % device_uri)
107 #log.warn("Unknown device URI: %s" % device_uri)
108 return (device_uri, [])
110 h = devices[device_uri].history.get()
111 log.debug("%d events in history:" % len(h))
112 [x.debug() for x in h]
113 return (device_uri, [x.as_tuple() for x in h])
116 @dbus.service.method('com.hplip.StatusService', in_signature='s', out_signature='sa{ss}')
117 def GetStatus(self, device_uri):
118 log.debug("GetStatus('%s')" % device_uri)
123 #log.warn("Unknown device URI: %s" % device_uri)
124 return (device_uri, {})
127 dq = devices[device_uri].dq
128 [t.setdefault(x, str(dq[x])) for x in dq.keys()]
130 return (device_uri, t)
133 @dbus.service.method('com.hplip.StatusService', in_signature='ssi', out_signature='i')
134 def SetCachedIntValue(self, device_uri, key, value):
135 log.debug("SetCachedIntValue('%s', '%s', %d)" % (device_uri, key, value))
136 if check_device(device_uri) == ERROR_SUCCESS:
137 devices[device_uri].cache[key] = value
143 @dbus.service.method('com.hplip.StatusService', in_signature='ss', out_signature='i')
144 def GetCachedIntValue(self, device_uri, key):
146 ret = devices[device_uri].cache[key]
150 log.debug("GetCachedIntValue('%s', '%s') --> %d" % (device_uri, key, ret))
154 @dbus.service.method('com.hplip.StatusService', in_signature='sss', out_signature='s')
155 def SetCachedStrValue(self, device_uri, key, value):
156 log.debug("SetCachedStrValue('%s', '%s', '%s')" % (device_uri, key, value))
157 if check_device(device_uri) == ERROR_SUCCESS:
158 devices[device_uri].cache[key] = value
164 @dbus.service.method('com.hplip.StatusService', in_signature='ss', out_signature='s')
165 def GetCachedStrValue(self, device_uri, key):
167 ret = devices[device_uri].cache[key]
171 log.debug("GetCachedStrValue('%s', '%s') --> %s" % (device_uri, key, ret))
175 # Pass a non-zero job_id to retrieve a specific fax
176 # Pass zero for job_id to retrieve any avail. fax
177 @dbus.service.method('com.hplip.StatusService', in_signature='ssi', out_signature='ssisisds')
178 def CheckForWaitingFax(self, device_uri, username, job_id=0):
179 log.debug("CheckForWaitingFax('%s', '%s', %d)" % (device_uri, username, job_id))
181 r = (device_uri, '', 0, username, job_id, '', 0.0, '')
182 check_device(device_uri)
183 show_waiting_faxes(device_uri)
185 if job_id: # check for specific job_id
187 devices[device_uri].faxes[(username, job_id)]
191 return self.check_for_waiting_fax_return(device_uri, username, job_id)
193 else: # return any matching one from cache. call mult. times to get all.
194 for u, j in devices[device_uri].faxes.keys():
196 return self.check_for_waiting_fax_return(device_uri, u, j)
201 # if CheckForWaitingFax returns a fax job, that job is removed from the cache
202 def check_for_waiting_fax_return(self, d, u, j):
203 log.debug("Fax (username=%s, jobid=%d) removed from faxes and returned to caller." % (u, j))
204 r = devices[d].faxes[(u, j)].as_tuple()
205 del devices[d].faxes[(u, j)]
206 show_waiting_faxes(d)
210 # Alternate way to "send" an event rather than using a signal message
211 @dbus.service.method('com.hplip.StatusService', in_signature='ssisis', out_signature='')
212 def SendEvent(self, device_uri, printer_name, event_code, username, job_id, title):
213 event = device.Event(device_uri, printer_name, event_code, username, job_id, title)
218 def check_device(device_uri):
222 log.debug("New device: %s" % device_uri)
224 back_end, is_hp, bus, model, serial, dev_file, host, zc, port = \
225 device.parseDeviceURI(device_uri)
227 log.debug("Invalid device URI: %s" % device_uri)
228 return ERROR_INVALID_DEVICE_URI
230 devices[device_uri] = DeviceCache(model)
235 def create_history(event):
236 history = devices[event.device_uri].history.get()
238 if history and history[-1].event_code == event.event_code:
239 log.debug("Duplicate event. Replacing previous event.")
240 devices[event.device_uri].history.replace(event)
243 devices[event.device_uri].history.append(event)
248 def handle_fax_event(event, pipe_name):
249 if event.event_code == EVENT_FAX_RENDER_COMPLETE and \
250 event.username == prop.username:
252 fax_file_fd, fax_file_name = tempfile.mkstemp(prefix="hpfax-")
253 pipe = os.open(pipe_name, os.O_RDONLY)
256 data = os.read(pipe, PIPE_BUF)
260 os.write(fax_file_fd, data)
261 bytes_read += len(data)
263 log.debug("Saved %d bytes to file %s" % (bytes_read, fax_file_name))
266 os.close(fax_file_fd)
268 devices[event.device_uri].faxes[(event.username, event.job_id)] = \
269 device.FaxEvent(fax_file_name, event)
271 show_waiting_faxes(event.device_uri)
274 os.waitpid(-1, os.WNOHANG)
278 # See if hp-sendfax is already running for this queue
279 ok, lock_file = utils.lock_app('hp-sendfax-%s' % event.printer_name, True)
282 # able to lock, not running...
283 utils.unlock(lock_file)
285 path = utils.which('hp-sendfax')
287 path = os.path.join(path, 'hp-sendfax')
289 log.error("Unable to find hp-sendfax on PATH.")
292 log.debug("Running hp-sendfax: %s --printer=%s" % (path, event.printer_name))
293 os.spawnlp(os.P_NOWAIT, path, 'hp-sendfax',
294 '--printer=%s' % event.printer_name)
297 # cannot lock file - hp-sendfax is running
298 # no need to do anything... hp-sendfax is polling
299 log.debug("hp-sendfax is running. Waiting for CheckForWaitingFax() call.")
302 log.warn("Not handled!")
306 def show_waiting_faxes(d):
310 log.debug("No faxes waiting for %s" % d)
313 log.debug("1 fax waiting for %s:" % d)
315 log.debug("%d faxes waiting for %s:" % (len(f), d))
317 [f[x].debug() for x in f]
321 def handle_hpdio_event(event, bytes_written):
322 log.debug("Reading %d bytes from hpdio pipe..." % bytes_written)
323 total_read, data = 0, ''
326 r, w, e = select.select([r3], [], [r3], 0.0)
329 x = os.read(r3, PIPE_BUF)
332 data = ''.join([data, x])
335 if total_read == bytes_written: break
337 log.debug("Read %d bytes" % total_read)
339 if total_read == bytes_written:
342 if check_device(event.device_uri) == ERROR_SUCCESS:
343 devices[event.device_uri].dq = dq.copy()
345 handle_event(device.Event(event.device_uri, '',
346 dq.get('status-code', STATUS_PRINTER_IDLE), prop.username, 0, ''))
348 send_toolbox_event(event, EVENT_DEVICE_UPDATE_REPLY)
350 def handle_plugin_install():
352 child_process=os.fork()
353 if child_process== 0: # child process
354 lockObj = utils.Sync_Lock("/tmp/pluginInstall.tmp")
356 child_pid=os.getpid()
357 core = core_install.CoreInstall()
358 core.set_plugin_version()
359 if core.check_for_plugin() != PLUGIN_INSTALLED:
360 sts,out = utils.run('hp-diagnose_plugin',True, None, 1, False)
362 log.error("Failed to load hp-diagnose_plugin")
363 #TBD FailureUI needs to add
365 log.debug("Device Plug-in was already installed. Not Invoking Plug-in installation wizard")
368 os.kill(child_pid,signal.SIGKILL)
369 else: #parent process
370 log.debug("Started Plug-in installation wizard")
373 def handle_printer_diagnose():
374 path = utils.which('hp-diagnose_queues')
376 path = os.path.join(path, 'hp-diagnose_queues')
378 log.error("Unable to find hp-diagnose_queues on PATH.")
381 log.debug("Running hp-diagnose_queues: %s" % (path))
382 os.spawnlp(os.P_NOWAIT, path, 'hp-diagnose_queues','-s')
385 def handle_event(event, more_args=None):
386 #global polling_blocked
387 #global request_queue
389 # checking if any zombie child process exists. then cleaning same.
391 os.waitpid(0, os.WNOHANG)
395 log.debug("Handling event...")
397 if more_args is None:
402 if event.event_code == EVENT_AUTO_CONFIGURE:
403 handle_plugin_install()
406 if event.event_code == EVENT_DIAGNOSE_PRINTQUEUE:
407 handle_printer_diagnose()
410 if event.device_uri and check_device(event.device_uri) != ERROR_SUCCESS:
413 # If event-code > 10001, its a PJL error code, so convert it
414 if event.event_code > EVENT_MAX_EVENT:
415 event.event_code = status.MapPJLErrorCode(event.event_code)
417 # regular user/device status event
418 if event.event_code < EVENT_MIN_USER_EVENT:
421 elif EVENT_MIN_USER_EVENT <= event.event_code <= EVENT_MAX_USER_EVENT:
424 #event.device_uri = event.device_uri.replace('hpfax:', 'hp:')
425 dup_event = create_history(event)
427 if event.event_code in (EVENT_DEVICE_STOP_POLLING,
428 EVENT_START_MAINT_JOB,
429 EVENT_START_COPY_JOB,
431 EVENT_START_PRINT_JOB):
432 pass # stop polling (increment counter)
434 elif event.event_code in (EVENT_DEVICE_START_POLLING, # should this event force counter to 0?
439 EVENT_PRINT_FAILED_MISSING_PLUGIN,
442 EVENT_SCAN_FAILED_MISSING_PLUGIN,
444 EVENT_FAX_JOB_CANCELED,
445 EVENT_FAX_FAILED_MISSING_PLUGIN,
447 EVENT_COPY_JOB_CANCELED):
448 pass # start polling if counter <= 0
449 # TODO: Do tools send END event if canceled or failed? Should they?
450 # TODO: What to do if counter doesn't hit 0 after a period? Timeout?
451 # TODO: Also, need to deal with the backoff setting (or it completely sep?)
453 # Send to system tray icon if available
454 if not dup_event: # and event.event_code != STATUS_PRINTER_IDLE:
455 send_event_to_systray_ui(event)
457 # send EVENT_HISTORY_UPDATE signal to hp-toolbox
458 send_toolbox_event(event, EVENT_HISTORY_UPDATE)
460 if event.event_code in (EVENT_PRINT_FAILED_MISSING_PLUGIN, EVENT_SCAN_FAILED_MISSING_PLUGIN,EVENT_FAX_FAILED_MISSING_PLUGIN):
461 handle_plugin_install()
464 elif EVENT_FAX_MIN <= event.event_code <= EVENT_FAX_MAX and more_args:
465 log.debug("Fax event")
466 pipe_name = str(more_args[0])
467 handle_fax_event(event, pipe_name)
469 elif event.event_code == EVENT_USER_CONFIGURATION_CHANGED:
470 # Sent if polling, hiding, etc. configuration has changed
471 # send_event_to_hpdio(event)
472 send_event_to_systray_ui(event)
474 elif event.event_code == EVENT_SYS_CONFIGURATION_CHANGED: # Not implemented
475 #send_event_to_hpdio(event)
476 send_event_to_systray_ui(event)
479 elif event.event_code in (EVENT_DEVICE_UPDATE_REQUESTED,):
480 #EVENT_DEVICE_START_POLLING, # ? Who handles polling? hpssd? probably...
481 #EVENT_DEVICE_STOP_POLLING): # ?
482 send_event_to_hpdio(event)
485 elif event.event_code in (EVENT_DEVICE_UPDATE_ACTIVE,
486 EVENT_DEVICE_UPDATE_INACTIVE):
487 send_event_to_systray_ui(event)
490 elif event.event_code == EVENT_DEVICE_UPDATE_REPLY:
491 bytes_written = int(more_args[1])
492 handle_hpdio_event(event, bytes_written)
495 elif event.event_code == EVENT_SYSTEMTRAY_EXIT:
496 send_event_to_hpdio(event)
497 send_toolbox_event(event)
498 send_event_to_systray_ui(event)
502 elif event.event_code in (EVENT_DEVICE_START_POLLING,
503 EVENT_DEVICE_STOP_POLLING):
507 log.error("Unhandled event: %d" % event.event_code)
511 def send_systray_blip():
512 send_event_to_systray_ui(device.Event('', '', EVENT_DEVICE_UPDATE_BLIP))
515 def send_event_to_systray_ui(event, event_code=None):
518 if event_code is not None:
519 e.event_code = event_code
521 e.send_via_pipe(w1, 'systemtray')
524 def send_event_to_hpdio(event):
525 event.send_via_pipe(w2, 'hpdio')
528 def send_toolbox_event(event, event_code=None):
533 if event_code is not None:
534 e.event_code = event_code
536 e.send_via_dbus(session_bus, 'com.hplip.Toolbox')
540 def handle_signal(typ, *args, **kwds):
541 if kwds['interface'] == 'com.hplip.StatusService' and \
542 kwds['member'] == 'Event':
544 event = device.Event(*args[:6])
545 return handle_event(event, args[6:])
548 def handle_system_signal(*args, **kwds):
549 return handle_signal('system', *args, **kwds)
552 def handle_session_signal(*args, **kwds):
553 return handle_signal('session', *args, **kwds)
557 def run(write_pipe1=None, # write pipe to systemtray
558 write_pipe2=None, # write pipe to hpdio
559 read_pipe3=None): # read pipe from hpdio
561 global dbus_loop, main_loop
562 global system_bus, session_bus
565 log.set_module("hp-systray(hpssd)")
566 log.debug("PID=%d" % os.getpid())
567 w1, w2, r3 = write_pipe1, write_pipe2, read_pipe3
569 dbus_loop = DBusGMainLoop(set_as_default=True)
570 main_loop = MainLoop()
573 system_bus = SystemBus(mainloop=dbus_loop)
574 except dbus.exceptions.DBusException, e:
575 log.error("Unable to connect to dbus system bus. Exiting.")
579 session_bus = dbus.SessionBus()
580 except dbus.exceptions.DBusException, e:
582 log.error("Unable to connect to dbus session bus. Exiting.")
585 log.error("Unable to connect to dbus session bus (running as root?)")
588 # Receive events from the system bus
589 system_bus.add_signal_receiver(handle_system_signal, sender_keyword='sender',
590 destination_keyword='dest', interface_keyword='interface',
591 member_keyword='member', path_keyword='path')
593 # Receive events from the session bus
594 session_bus.add_signal_receiver(handle_session_signal, sender_keyword='sender',
595 destination_keyword='dest', interface_keyword='interface',
596 member_keyword='member', path_keyword='path')
598 # Export an object on the session bus
599 session_name = dbus.service.BusName("com.hplip.StatusService", session_bus)
600 status_service = StatusService(session_name, "/com/hplip/StatusService")
602 log.debug("Entering main dbus loop...")
605 except KeyboardInterrupt:
606 log.debug("Ctrl-C: Exiting...")