diff --git a/base_report_to_printer/printing.py b/base_report_to_printer/printing.py index daff378d8ca..0a8b89ff52e 100644 --- a/base_report_to_printer/printing.py +++ b/base_report_to_printer/printing.py @@ -24,195 +24,14 @@ import logging import os -from contextlib import contextmanager -from datetime import datetime from tempfile import mkstemp -from threading import Thread import cups -import psycopg2 -from openerp import models, fields, api, sql_db -from openerp.tools import ormcache +from openerp import models, fields, api _logger = logging.getLogger(__name__) -POLL_INTERVAL = 15 # seconds - - -class PrintingPrinterPolling(models.Model): - """ Keep the last update time of printers update. - - This table will contain only 1 row, with the last time we checked - the list of printers from cups. - - The table is locked before an update so 2 processes won't be able - to do the update at the same time. - """ - _name = 'printing.printer.polling' - _description = 'Printers Polling' - - last_update = fields.Datetime() - - @api.model - def find_unique_record(self): - polling = self.search([], limit=1) - return polling - - @api.model - @ormcache() - def table_exists(self): - return self._model._table_exist(self.env.cr) - - def _create_table(self, cr): - super(PrintingPrinterPolling, self)._create_table(cr) - self.clear_caches() - - @api.model - def find_or_create_unique_record(self): - polling = self.find_unique_record() - if polling: - return polling - cr = self.env.cr - try: - # Will be released at the end of the transaction. Locks the - # full table for insert/update because we must have only 1 - # record in this table, so we prevent 2 processes to create - # each one one line at the same time. - cr.execute("LOCK TABLE %s IN SHARE ROW EXCLUSIVE MODE NOWAIT" % - self._table, log_exceptions=False) - except psycopg2.OperationalError as err: - # the lock could not be acquired, already running - if err.pgcode == '55P03': - _logger.debug('Another process/thread is already ' - 'creating the polling record.') - return self.browse() - else: - raise - return self.create({'last_update': False}) - - @api.multi - def lock(self): - """ Lock the polling record - - Lock the record in the database so we can prevent concurrent - processes to update at the same time. - - The lock is released either on commit or rollback of the - transaction. - - Returns if the record has been locked or not. - """ - self.ensure_one() - cr = self.env.cr - sql = ("SELECT id FROM %s WHERE id = %%s FOR UPDATE NOWAIT" % - self._table) - try: - cr.execute(sql, (self.id, ), log_exceptions=False) - except psycopg2.OperationalError as err: - # the lock could not be acquired, already running - if err.pgcode == '55P03': - _logger.debug('Another process/thread is already ' - 'updating the printers list.') - return False - if err.pgcode == '40001': - _logger.debug('could not serialize access due to ' - 'concurrent update') - return False - else: - raise - return True - - @contextmanager - @api.model - def start_update(self): - locked = False - polling = self.find_or_create_unique_record() - if polling: - if polling.lock(): - locked = True - yield locked - if locked: - polling.write({'last_update': fields.Datetime.now()}) - - @ormcache() - def _last_update_cached(self): - """ Get the last update's datetime, the returned value is cached """ - polling = self.find_unique_record() - if not polling: - return False - last_update = polling.last_update - if last_update: - last_update = fields.Datetime.from_string(last_update) - return last_update - - @api.model - def last_update_cached(self): - """ Returns the last update datetime from a cache - - The check if the list of printers needs to be refreshed is - called very often (each time a browse is done on ``res.users``), - so we avoid to hit the database on every updates by keeping the - last value in cache. - The cache has no expiration so we manually clear it when the - poll interval (defaulted to 10 seconds) is reached. - """ - last_update = self._last_update_cached() - now = datetime.now() - if last_update and (now - last_update).seconds >= POLL_INTERVAL: - # Invalidates last_update_cached and read a fresh value - # from the database - self.clear_caches() - return self._last_update_cached() - return last_update - - @api.model - def need_update(self): - last_update = self.last_update_cached() - now = datetime.now() - # Only update printer status if current status is more than 10 - # seconds old. - if not last_update or (now - last_update).seconds >= POLL_INTERVAL: - self.clear_caches() # invalidates last_update_cached - return True - return False - - @api.model - def update_printers_status(self): - cr = sql_db.db_connect(self.env.cr.dbname).cursor() - uid, context = self.env.uid, self.env.context - with api.Environment.manage(): - try: - self.env = api.Environment(cr, uid, context) - printer_obj = self.env['printing.printer'] - with self.start_update() as locked: - if not locked: - return # could not obtain lock - - printer_recs = printer_obj.search([]) - - try: - connection = cups.Connection() - printers = connection.getPrinters() - except: - printer_recs.write({'status': 'server-error'}) - else: - for printer in printer_recs: - cups_printer = printers.get(printer.system_name) - if cups_printer: - printer.update_from_cups(connection, - cups_printer) - else: - # not in cups list - printer.status = 'unavailable' - - self.env.cr.commit() - except: - self.env.cr.rollback() - raise - finally: - self.env.cr.close() - class PrintingPrinter(models.Model): """ @@ -240,6 +59,24 @@ class PrintingPrinter(models.Model): location = fields.Char(readonly=True) uri = fields.Char(string='URI', readonly=True) + @api.model + def update_printers_status(self): + printer_recs = self.search([]) + try: + connection = cups.Connection() + printers = connection.getPrinters() + except: + printer_recs.write({'status': 'server-error'}) + else: + for printer in printer_recs: + cups_printer = printers.get(printer.system_name) + if cups_printer: + printer.update_from_cups(connection, cups_printer) + else: + # not in cups list + printer.status = 'unavailable' + return True + @api.multi def _prepare_update_from_cups(self, cups_connection, cups_printer): mapping = { @@ -264,8 +101,10 @@ def update_from_cups(self, cups_connection, cups_printer): :param cups_printer: dict of information returned by CUPS for the current printer """ + self.ensure_one() vals = self._prepare_update_from_cups(cups_connection, cups_printer) - self.write(vals) + if any(self[name] != value for name, value in vals.iteritems()): + self.write(vals) @api.multi def print_options(self, report, format): @@ -299,40 +138,6 @@ def print_document(self, report, content, format): _logger.info("Printing job: '%s'" % file_name) return True - @api.model - def start_printer_update(self): - polling_obj = self.env['printing.printer.polling'] - thread = Thread(target=polling_obj.update_printers_status, args=()) - thread.start() - - @api.model - def update(self): - """Update printer status if current status is more than 10s old.""" - polling_obj = self.env['printing.printer.polling'] - if not polling_obj.table_exists(): - # On the installation of the module, this method could be - # called before the 'printing.printer.polling' table exists - # (but the model already is in memory) - return - if polling_obj.need_update(): - self.start_printer_update() - return True - - @api.v7 - def browse(self, cr, uid, arg=None, context=None): - _super = super(PrintingPrinter, self) - recs = _super.browse(cr, uid, arg=arg, context=context) - if not recs._context.get('skip_update'): - recs.with_context(skip_update=True).update() - return recs - - @api.v8 - def browse(self, arg=None): - recs = super(PrintingPrinter, self).browse(arg=arg) - if not recs._context.get('skip_update'): - recs.with_context(skip_update=True).update() - return recs - @api.multi def set_default(self): if not self: diff --git a/base_report_to_printer/printing_data.xml b/base_report_to_printer/printing_data.xml index 8a950056dc7..5998d14b827 100644 --- a/base_report_to_printer/printing_data.xml +++ b/base_report_to_printer/printing_data.xml @@ -1,6 +1,6 @@ - + Send to Printer @@ -16,5 +16,19 @@ + + + Update Printers Status + + + 1 + minutes + -1 + + + + + +