# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2007 Ferran Pegueroles # Copyright (c) 2009 Albert Cervera i Areny # Copyright (C) 2011 Agile Business Group sagl () # Copyright (C) 2011 Domsense srl () # Copyright (C) 2013 Camptocamp () # All Rights Reserved # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 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 Affero General Public License # along with this program. If not, see . # ############################################################################## import time import cups from threading import Thread from threading import Lock from openerp import pooler from openerp.osv import orm, fields from openerp.tools.translate import _ class printing_printer(orm.Model): """ Printers """ _name = "printing.printer" _description = "Printer" _columns = { 'name': fields.char( 'Name', size=64, required=True, select="1"), 'system_name': fields.char( 'System Name', size=64, required=True, select="1"), 'default': fields.boolean( 'Default Printer', readonly=True), 'status': fields.selection( [('unavailable', 'Unavailable'), ('printing', 'Printing'), ('unknown', 'Unknown'), ('available', 'Available'), ('error', 'Error'), ('server-error', 'Server Error')], 'Status', required=True, readonly=True), 'status_message': fields.char( 'Status Message', size=500, readonly=True), 'model': fields.char( 'Model', size=500, readonly=True), 'location': fields.char( 'Location', size=500, readonly=True), 'uri': fields.char( 'URI', size=500, readonly=True), } _order = "name" _defaults = { 'default': lambda *a: False, 'status': lambda *a: 'unknown', } def __init__(self, pool, cr): super(printing_printer, self).__init__(pool, cr) self.lock = Lock() self.last_update = None self.updating = False def update_printers_status(self, db_name, uid, context=None): db, pool = pooler.get_db_and_pool(db_name) cr = db.cursor() try: connection = cups.Connection() printers = connection.getPrinters() server_error = False except: server_error = True mapping = { 3: 'available', 4: 'printing', 5: 'error' } if context is None: context = {} try: # Skip update to avoid the thread being created again ctx = context.copy() ctx['skip_update'] = True ids = self.search(cr, uid, [], context=ctx) for printer in self.browse(cr, uid, ids, context=ctx): vals = {} if server_error: status = 'server-error' elif printer.system_name in printers: info = printers[printer.system_name] status = mapping.get(info['printer-state'], 'unknown') vals = { 'model': info.get('printer-make-and-model', False), 'location': info.get('printer-location', False), 'uri': info.get('device-uri', False), } else: status = 'unavailable' vals['status'] = status self.write(cr, uid, [printer.id], vals, context=context) cr.commit() except: cr.rollback() raise finally: cr.close() with self.lock: self.updating = False self.last_update = time.time() def start_printer_update(self, cr, uid, context): self.lock.acquire() if self.updating: self.lock.release() return self.updating = True self.lock.release() thread = Thread(target=self.update_printers_status, args=(cr.dbname, uid, context.copy())) thread.start() def update(self, cr, uid, context=None): """Update printer status if current status is more than 10s old.""" # We won't acquire locks - we're only assigning from immutable data if not context or 'skip_update' in context: return True last_update = self.last_update now = time.time() # Only update printer status if current status is more than # 10 seconds old. if not last_update or now - last_update > 10: self.start_printer_update(cr, uid, context) # Wait up to five seconds for printer status update for _dummy in range(0, 5): time.sleep(1) if not self.updating: break return True def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): self.update(cr, uid, context) return super(printing_printer, self ).search(cr, uid, args, offset, limit, order, context, count) def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): self.update(cr, uid, context) return super(printing_printer, self ).read(cr, uid, ids, fields, context, load) def browse(self, cr, uid, ids, context=None): self.update(cr, uid, context) return super(printing_printer, self).browse(cr, uid, ids, context) def set_default(self, cr, uid, ids, context): if not ids: return default_ids = self.search(cr, uid, [('default', '=', True)]) self.write(cr, uid, default_ids, {'default': False}, context) self.write(cr, uid, ids[0], {'default': True}, context) return True def get_default(self, cr, uid, context): printer_ids = self.search(cr, uid, [('default', '=', True)]) if printer_ids: return printer_ids[0] return False # # Actions # def _available_action_types(self, cr, uid, context=None): return [ ('server', _('Send to Printer')), ('client', _('Send to Client')), ('user_default', _("Use user's defaults")), ] class printing_action(orm.Model): _name = 'printing.action' _description = 'Print Job Action' _columns = { 'name': fields.char('Name', size=256, required=True), 'type': fields.selection(_available_action_types, 'Type', required=True), }