Fixed build error by changing buildrequires from pkgconfig(turbo-jpeg) to libjpeg...
[platform/upstream/hplip.git] / hpssd.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
5 #
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.
10 #
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.
15 #
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
19 #
20 # Author: Don Welch
21 #
22
23 __version__ = '12.0'
24 __title__ = "Services and Status System Tray dBus Child/Parent Process"
25 __mod__ = 'hpssd'
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."
27
28
29 # StdLib
30 import sys
31 import struct
32 import os
33 import time
34 import getopt
35 import select
36 import signal
37 import tempfile
38 #import threading
39 #import Queue
40 from cPickle import loads, HIGHEST_PROTOCOL
41
42 # Local
43 from base.g import *
44 from base.codes import *
45 from base import utils, device, status, models, module
46 from installer import core_install
47
48 # dBus
49 try:
50     from dbus import lowlevel, SystemBus, SessionBus
51     import dbus.service
52     from dbus.mainloop.glib import DBusGMainLoop
53     from gobject import MainLoop, timeout_add, threads_init, io_add_watch, IO_IN
54     dbus_loaded = True
55 except ImportError:
56     log.error("dbus failed to load (python-dbus ver. 0.80+ required). Exiting...")
57     dbus_loaded = False
58     sys.exit(1)
59
60 import warnings
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)
64
65
66 # Globals
67 PIPE_BUF = 4096
68 dbus_loop, main_loop = None, None
69 system_bus = None
70 session_bus = None
71 w1, w2, r3 = None, None, None
72 devices = {} # { 'device_uri' : DeviceCache, ... }
73
74
75 # ***********************************************************************************
76 #
77 # DEVICE CACHE
78 #
79 # ***********************************************************************************
80
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
88         #self.backoff = False
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
92
93
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)
98
99
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)
103         send_systray_blip()
104         try:
105             devices[device_uri]
106         except KeyError:
107             #log.warn("Unknown device URI: %s" % device_uri)
108             return (device_uri, [])
109         else:
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])
114
115
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)
119         send_systray_blip()
120         try:
121             devices[device_uri]
122         except KeyError:
123             #log.warn("Unknown device URI: %s" % device_uri)
124             return (device_uri, {})
125         else:
126             t = {}
127             dq = devices[device_uri].dq
128             [t.setdefault(x, str(dq[x])) for x in dq.keys()]
129             log.debug(t)
130             return (device_uri, t)
131
132
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
138             return value
139
140         return -1
141
142
143     @dbus.service.method('com.hplip.StatusService', in_signature='ss', out_signature='i')
144     def GetCachedIntValue(self, device_uri, key):
145         try:
146             ret = devices[device_uri].cache[key]
147         except KeyError:
148             ret = -1
149
150         log.debug("GetCachedIntValue('%s', '%s') --> %d" % (device_uri, key, ret))
151         return ret
152
153
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
159             return value
160
161         return ''
162
163
164     @dbus.service.method('com.hplip.StatusService', in_signature='ss', out_signature='s')
165     def GetCachedStrValue(self, device_uri, key):
166         try:
167             ret = devices[device_uri].cache[key]
168         except KeyError:
169             ret = ''
170
171         log.debug("GetCachedStrValue('%s', '%s') --> %s" % (device_uri, key, ret))
172         return ret
173
174
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))
180         send_systray_blip()
181         r = (device_uri, '', 0, username, job_id, '', 0.0, '')
182         check_device(device_uri)
183         show_waiting_faxes(device_uri)
184
185         if job_id: # check for specific job_id
186             try:
187                 devices[device_uri].faxes[(username, job_id)]
188             except KeyError:
189                 return r
190             else:
191                 return self.check_for_waiting_fax_return(device_uri, username, job_id)
192
193         else: # return any matching one from cache. call mult. times to get all.
194             for u, j in devices[device_uri].faxes.keys():
195                 if u == username:
196                     return self.check_for_waiting_fax_return(device_uri, u, j)
197
198             return r
199
200
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)
207         return r
208
209
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)
214         handle_event(event)
215
216
217
218 def check_device(device_uri):
219     try:
220         devices[device_uri]
221     except KeyError:
222         log.debug("New device: %s" % device_uri)
223         try:
224             back_end, is_hp, bus, model, serial, dev_file, host, zc, port = \
225                 device.parseDeviceURI(device_uri)
226         except Error:
227             log.debug("Invalid device URI: %s" % device_uri)
228             return ERROR_INVALID_DEVICE_URI
229
230         devices[device_uri] = DeviceCache(model)
231
232     return ERROR_SUCCESS
233
234
235 def create_history(event):
236     history = devices[event.device_uri].history.get()
237
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)
241         return True
242     else:
243         devices[event.device_uri].history.append(event)
244         return False
245
246
247
248 def handle_fax_event(event, pipe_name):
249     if event.event_code == EVENT_FAX_RENDER_COMPLETE and \
250         event.username == prop.username:
251
252         fax_file_fd, fax_file_name = tempfile.mkstemp(prefix="hpfax-")
253         pipe = os.open(pipe_name, os.O_RDONLY)
254         bytes_read = 0
255         while True:
256             data = os.read(pipe, PIPE_BUF)
257             if not data:
258                 break
259
260             os.write(fax_file_fd, data)
261             bytes_read += len(data)
262
263         log.debug("Saved %d bytes to file %s" % (bytes_read, fax_file_name))
264
265         os.close(pipe)
266         os.close(fax_file_fd)
267
268         devices[event.device_uri].faxes[(event.username, event.job_id)] = \
269             device.FaxEvent(fax_file_name, event)
270
271         show_waiting_faxes(event.device_uri)
272
273         try:
274             os.waitpid(-1, os.WNOHANG)
275         except OSError:
276             pass
277
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)
280
281         if ok:
282             # able to lock, not running...
283             utils.unlock(lock_file)
284
285             path = utils.which('hp-sendfax')
286             if path:
287                 path = os.path.join(path, 'hp-sendfax')
288             else:
289                 log.error("Unable to find hp-sendfax on PATH.")
290                 return
291
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)
295
296         else:
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.")
300
301     else:
302         log.warn("Not handled!")
303         pass
304
305
306 def show_waiting_faxes(d):
307     f = devices[d].faxes
308
309     if not len(f):
310         log.debug("No faxes waiting for %s" % d)
311     else:
312         if len(f) == 1:
313             log.debug("1 fax waiting for %s:" % d)
314         else:
315             log.debug("%d faxes waiting for %s:" % (len(f), d))
316
317         [f[x].debug() for x in f]
318
319
320 # Qt4 only
321 def handle_hpdio_event(event, bytes_written):
322     log.debug("Reading %d bytes from hpdio pipe..." % bytes_written)
323     total_read, data = 0, ''
324
325     while True:
326         r, w, e = select.select([r3], [], [r3], 0.0)
327         if not r: break
328
329         x = os.read(r3, PIPE_BUF)
330         if not x: break
331
332         data = ''.join([data, x])
333         total_read += len(x)
334
335         if total_read == bytes_written: break
336
337     log.debug("Read %d bytes" % total_read)
338
339     if total_read == bytes_written:
340         dq = loads(data)
341
342         if check_device(event.device_uri) == ERROR_SUCCESS:
343             devices[event.device_uri].dq = dq.copy()
344
345             handle_event(device.Event(event.device_uri, '',
346                 dq.get('status-code', STATUS_PRINTER_IDLE), prop.username, 0, ''))
347
348             send_toolbox_event(event, EVENT_DEVICE_UPDATE_REPLY)
349
350 def handle_plugin_install():
351     
352     child_process=os.fork()
353     if child_process== 0:       # child process
354         lockObj = utils.Sync_Lock("/tmp/pluginInstall.tmp")
355         lockObj.acquire()
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)
361             if sts != 0:
362                 log.error("Failed to load hp-diagnose_plugin")
363                 #TBD FailureUI needs to add
364         else:   
365             log.debug("Device Plug-in was already installed. Not Invoking Plug-in installation wizard")
366
367         lockObj.release()
368         os.kill(child_pid,signal.SIGKILL)
369     else: #parent process
370         log.debug("Started Plug-in installation wizard")
371     
372
373 def handle_printer_diagnose():
374     path = utils.which('hp-diagnose_queues')
375     if path:
376         path = os.path.join(path, 'hp-diagnose_queues')
377     else:
378         log.error("Unable to find hp-diagnose_queues on PATH.")
379         return
380
381     log.debug("Running hp-diagnose_queues: %s" % (path))
382     os.spawnlp(os.P_NOWAIT, path, 'hp-diagnose_queues','-s')
383
384
385 def handle_event(event, more_args=None):
386     #global polling_blocked
387     #global request_queue
388
389    # checking if any zombie child process exists. then cleaning same.
390     try:
391         os.waitpid(0, os.WNOHANG)
392     except OSError:
393         pass
394
395     log.debug("Handling event...")
396
397     if more_args is None:
398         more_args = []
399
400     event.debug()
401  
402     if event.event_code == EVENT_AUTO_CONFIGURE:
403         handle_plugin_install()
404         return
405
406     if event.event_code == EVENT_DIAGNOSE_PRINTQUEUE:
407         handle_printer_diagnose()
408         return
409         
410     if event.device_uri and check_device(event.device_uri) != ERROR_SUCCESS:
411         return
412
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)
416
417     # regular user/device status event
418     if event.event_code < EVENT_MIN_USER_EVENT:
419         pass
420
421     elif EVENT_MIN_USER_EVENT <= event.event_code <= EVENT_MAX_USER_EVENT:
422
423         if event.device_uri:
424             #event.device_uri = event.device_uri.replace('hpfax:', 'hp:')
425             dup_event = create_history(event)
426
427             if event.event_code in (EVENT_DEVICE_STOP_POLLING,
428                                     EVENT_START_MAINT_JOB,
429                                     EVENT_START_COPY_JOB,
430                                     EVENT_START_FAX_JOB,
431                                     EVENT_START_PRINT_JOB):
432                 pass # stop polling (increment counter)
433
434             elif event.event_code in (EVENT_DEVICE_START_POLLING, # should this event force counter to 0?
435                                       EVENT_END_MAINT_JOB,
436                                       EVENT_END_COPY_JOB,
437                                       EVENT_END_FAX_JOB,
438                                       EVENT_END_PRINT_JOB,
439                                       EVENT_PRINT_FAILED_MISSING_PLUGIN,
440                                       EVENT_SCANNER_FAIL,
441                                       EVENT_END_SCAN_JOB,
442                                       EVENT_SCAN_FAILED_MISSING_PLUGIN,
443                                       EVENT_FAX_JOB_FAIL,
444                                       EVENT_FAX_JOB_CANCELED,
445                                       EVENT_FAX_FAILED_MISSING_PLUGIN,
446                                       EVENT_COPY_JOB_FAIL,
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?)
452
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)
456
457         # send EVENT_HISTORY_UPDATE signal to hp-toolbox
458         send_toolbox_event(event, EVENT_HISTORY_UPDATE)
459         
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()
462
463     # Handle fax signals
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)
468
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)
473
474     elif event.event_code == EVENT_SYS_CONFIGURATION_CHANGED: # Not implemented
475         #send_event_to_hpdio(event)
476         send_event_to_systray_ui(event)
477
478     # Qt4 only
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)
483
484     # Qt4 only
485     elif event.event_code in (EVENT_DEVICE_UPDATE_ACTIVE,
486                               EVENT_DEVICE_UPDATE_INACTIVE):
487         send_event_to_systray_ui(event)
488
489     # Qt4 only
490     elif event.event_code == EVENT_DEVICE_UPDATE_REPLY:
491         bytes_written = int(more_args[1])
492         handle_hpdio_event(event, bytes_written)
493
494     # Qt4 only
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)
499         log.debug("Exiting")
500         main_loop.quit()
501
502     elif event.event_code in (EVENT_DEVICE_START_POLLING,
503                               EVENT_DEVICE_STOP_POLLING):
504         pass
505
506     else:
507         log.error("Unhandled event: %d" % event.event_code)
508
509
510
511 def send_systray_blip():
512     send_event_to_systray_ui(device.Event('', '', EVENT_DEVICE_UPDATE_BLIP))
513
514
515 def send_event_to_systray_ui(event, event_code=None):
516     e = event.copy()
517
518     if event_code is not None:
519         e.event_code = event_code
520
521     e.send_via_pipe(w1, 'systemtray')
522
523
524 def send_event_to_hpdio(event):
525     event.send_via_pipe(w2, 'hpdio')
526
527
528 def send_toolbox_event(event, event_code=None):
529     global session_bus
530
531     e = event.copy()
532
533     if event_code is not None:
534         e.event_code = event_code
535
536     e.send_via_dbus(session_bus, 'com.hplip.Toolbox')
537
538
539
540 def handle_signal(typ, *args, **kwds):
541     if kwds['interface'] == 'com.hplip.StatusService' and \
542         kwds['member'] == 'Event':
543
544         event = device.Event(*args[:6])
545         return handle_event(event, args[6:])
546
547
548 def handle_system_signal(*args, **kwds):
549     return handle_signal('system', *args, **kwds)
550
551
552 def handle_session_signal(*args, **kwds):
553     return handle_signal('session', *args, **kwds)
554
555
556
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
560         
561     global dbus_loop, main_loop
562     global system_bus, session_bus
563     global w1, w2, r3
564
565     log.set_module("hp-systray(hpssd)")
566     log.debug("PID=%d" % os.getpid())
567     w1, w2, r3 = write_pipe1, write_pipe2, read_pipe3
568
569     dbus_loop = DBusGMainLoop(set_as_default=True)
570     main_loop = MainLoop()
571
572     try:
573         system_bus = SystemBus(mainloop=dbus_loop)
574     except dbus.exceptions.DBusException, e:
575         log.error("Unable to connect to dbus system bus. Exiting.")
576         sys.exit(1)
577
578     try:
579         session_bus = dbus.SessionBus()
580     except dbus.exceptions.DBusException, e:
581         if os.getuid() != 0:
582             log.error("Unable to connect to dbus session bus. Exiting.")
583             sys.exit(1)
584         else:
585             log.error("Unable to connect to dbus session bus (running as root?)")
586             sys.exit(1)
587
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')
592
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')
597
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")
601
602     log.debug("Entering main dbus loop...")
603     try:
604         main_loop.run()
605     except KeyboardInterrupt:
606         log.debug("Ctrl-C: Exiting...")
607