2009-10-20 10:52:23 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2006-12-07 13:41:40 +00:00
|
|
|
##############################################################################
|
|
|
|
#
|
2010-09-06 15:29:27 +00:00
|
|
|
# OpenERP, Open Source Management Solution
|
2010-12-08 14:17:07 +00:00
|
|
|
# Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
|
2008-06-16 11:00:21 +00:00
|
|
|
#
|
2008-11-03 18:27:16 +00:00
|
|
|
# This program is free software: you can redistribute it and/or modify
|
2010-12-08 14:17:07 +00:00
|
|
|
# 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.
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
2008-11-03 18:27:16 +00:00
|
|
|
# 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
|
2010-12-08 14:17:07 +00:00
|
|
|
# GNU Affero General Public License for more details.
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
2010-12-08 14:17:07 +00:00
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
2008-11-03 18:27:16 +00:00
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
|
|
|
##############################################################################
|
|
|
|
|
|
|
|
import time
|
2011-05-11 16:07:07 +00:00
|
|
|
import logging
|
2009-12-14 15:21:04 +00:00
|
|
|
from datetime import datetime
|
2009-12-14 12:23:55 +00:00
|
|
|
from dateutil.relativedelta import relativedelta
|
2006-12-07 13:41:40 +00:00
|
|
|
import netsvc
|
|
|
|
import tools
|
2010-05-03 23:49:53 +00:00
|
|
|
from tools.safe_eval import safe_eval as eval
|
2006-12-07 13:41:40 +00:00
|
|
|
import pooler
|
2009-10-08 19:50:28 +00:00
|
|
|
from osv import fields, osv
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2009-10-02 16:00:07 +00:00
|
|
|
def str2tuple(s):
|
2009-11-04 10:51:34 +00:00
|
|
|
return eval('tuple(%s)' % (s or ''))
|
2009-10-02 16:00:07 +00:00
|
|
|
|
2006-12-07 13:41:40 +00:00
|
|
|
_intervalTypes = {
|
2009-12-13 18:16:01 +00:00
|
|
|
'work_days': lambda interval: relativedelta(days=interval),
|
|
|
|
'days': lambda interval: relativedelta(days=interval),
|
|
|
|
'hours': lambda interval: relativedelta(hours=interval),
|
|
|
|
'weeks': lambda interval: relativedelta(days=7*interval),
|
|
|
|
'months': lambda interval: relativedelta(months=interval),
|
|
|
|
'minutes': lambda interval: relativedelta(minutes=interval),
|
2006-12-07 13:41:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ir_cron(osv.osv, netsvc.Agent):
|
2011-01-17 21:31:08 +00:00
|
|
|
""" This is the ORM object that periodically executes actions.
|
|
|
|
Note that we use the netsvc.Agent()._logger member.
|
|
|
|
"""
|
2008-07-22 14:24:36 +00:00
|
|
|
_name = "ir.cron"
|
2010-12-10 22:42:58 +00:00
|
|
|
_order = 'name'
|
2008-07-22 14:24:36 +00:00
|
|
|
_columns = {
|
|
|
|
'name': fields.char('Name', size=60, required=True),
|
|
|
|
'user_id': fields.many2one('res.users', 'User', required=True),
|
|
|
|
'active': fields.boolean('Active'),
|
2010-09-29 10:37:26 +00:00
|
|
|
'interval_number': fields.integer('Interval Number',help="Repeat every x."),
|
2008-07-22 14:24:36 +00:00
|
|
|
'interval_type': fields.selection( [('minutes', 'Minutes'),
|
|
|
|
('hours', 'Hours'), ('work_days','Work Days'), ('days', 'Days'),('weeks', 'Weeks'), ('months', 'Months')], 'Interval Unit'),
|
2010-10-04 14:39:04 +00:00
|
|
|
'numbercall': fields.integer('Number of Calls', help='Number of time the function is called,\na negative number indicates no limit'),
|
|
|
|
'doall' : fields.boolean('Repeat Missed', help="Enable this if you want to execute missed occurences as soon as the server restarts."),
|
|
|
|
'nextcall' : fields.datetime('Next Execution Date', required=True, help="Next planned execution date for this scheduler"),
|
|
|
|
'model': fields.char('Object', size=64, help="Name of object whose function will be called when this scheduler will run. e.g. 'res.partener'"),
|
|
|
|
'function': fields.char('Function', size=64, help="Name of the method to be called on the object when this scheduler is executed."),
|
|
|
|
'args': fields.text('Arguments', help="Arguments to be passed to the method. e.g. (uid,)"),
|
2008-07-22 14:24:36 +00:00
|
|
|
'priority': fields.integer('Priority', help='0=Very Urgent\n10=Not urgent')
|
|
|
|
}
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
_defaults = {
|
|
|
|
'nextcall' : lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
|
|
|
|
'priority' : lambda *a: 5,
|
|
|
|
'user_id' : lambda obj,cr,uid,context: uid,
|
|
|
|
'interval_number' : lambda *a: 1,
|
|
|
|
'interval_type' : lambda *a: 'months',
|
|
|
|
'numbercall' : lambda *a: 1,
|
|
|
|
'active' : lambda *a: 1,
|
|
|
|
'doall' : lambda *a: 1
|
|
|
|
}
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2009-10-02 16:00:07 +00:00
|
|
|
def _check_args(self, cr, uid, ids, context=None):
|
|
|
|
try:
|
|
|
|
for this in self.browse(cr, uid, ids, context):
|
|
|
|
str2tuple(this.args)
|
2011-01-17 21:31:08 +00:00
|
|
|
except Exception:
|
2009-10-02 16:00:07 +00:00
|
|
|
return False
|
|
|
|
return True
|
2010-09-06 15:29:27 +00:00
|
|
|
|
2009-10-08 19:50:28 +00:00
|
|
|
_constraints = [
|
2009-10-02 16:00:07 +00:00
|
|
|
(_check_args, 'Invalid arguments', ['args']),
|
|
|
|
]
|
|
|
|
|
2011-05-06 10:01:35 +00:00
|
|
|
def _handle_callback_exception(self, cr, uid, model, func, args, job_id, job_exception):
|
|
|
|
cr.rollback()
|
2011-05-24 11:07:07 +00:00
|
|
|
logger=logging.getLogger('cron')
|
|
|
|
logger.exception("Call of self.pool.get('%s').%s(cr, uid, *%r) failed in Job %s" % (model, func, args, job_id))
|
2011-05-06 10:01:35 +00:00
|
|
|
|
|
|
|
def _callback(self, cr, uid, model, func, args, job_id):
|
2009-10-02 16:00:07 +00:00
|
|
|
args = str2tuple(args)
|
|
|
|
m = self.pool.get(model)
|
2008-07-22 14:24:36 +00:00
|
|
|
if m and hasattr(m, func):
|
|
|
|
f = getattr(m, func)
|
2009-10-02 16:00:07 +00:00
|
|
|
try:
|
2011-05-13 13:35:14 +00:00
|
|
|
netsvc.log('cron', (cr.dbname,uid,'*',model,func)+tuple(args), channel=logging.DEBUG,
|
|
|
|
depth=(None if self._logger.isEnabledFor(logging.DEBUG_RPC_ANSWER) else 1), fn='object.execute')
|
2009-10-02 16:00:07 +00:00
|
|
|
f(cr, uid, *args)
|
|
|
|
except Exception, e:
|
2011-05-06 10:01:35 +00:00
|
|
|
self._handle_callback_exception(cr, uid, model, func, args, job_id, e)
|
2009-10-02 16:00:07 +00:00
|
|
|
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2009-10-08 19:50:28 +00:00
|
|
|
def _poolJobs(self, db_name, check=False):
|
2008-07-22 14:24:36 +00:00
|
|
|
try:
|
2009-02-06 13:47:41 +00:00
|
|
|
db, pool = pooler.get_db_and_pool(db_name)
|
2008-07-22 14:24:36 +00:00
|
|
|
except:
|
2010-09-06 15:29:27 +00:00
|
|
|
return False
|
2009-11-23 17:07:34 +00:00
|
|
|
cr = db.cursor()
|
2009-10-02 16:00:07 +00:00
|
|
|
try:
|
|
|
|
if not pool._init:
|
2009-12-14 15:21:04 +00:00
|
|
|
now = datetime.now()
|
2009-03-04 23:53:28 +00:00
|
|
|
cr.execute('select * from ir_cron where numbercall<>0 and active and nextcall<=now() order by priority')
|
|
|
|
for job in cr.dictfetchall():
|
2009-12-14 15:21:04 +00:00
|
|
|
nextcall = datetime.strptime(job['nextcall'], '%Y-%m-%d %H:%M:%S')
|
2009-03-04 23:53:28 +00:00
|
|
|
numbercall = job['numbercall']
|
2010-09-06 15:29:27 +00:00
|
|
|
|
2009-03-04 23:53:28 +00:00
|
|
|
ok = False
|
|
|
|
while nextcall < now and numbercall:
|
|
|
|
if numbercall > 0:
|
|
|
|
numbercall -= 1
|
|
|
|
if not ok or job['doall']:
|
2011-05-06 10:01:35 +00:00
|
|
|
self._callback(cr, job['user_id'], job['model'], job['function'], job['args'], job['id'])
|
2009-03-04 23:53:28 +00:00
|
|
|
if numbercall:
|
|
|
|
nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
|
|
|
|
ok = True
|
2009-10-08 19:50:28 +00:00
|
|
|
addsql = ''
|
2009-03-04 23:53:28 +00:00
|
|
|
if not numbercall:
|
|
|
|
addsql = ', active=False'
|
|
|
|
cr.execute("update ir_cron set nextcall=%s, numbercall=%s"+addsql+" where id=%s", (nextcall.strftime('%Y-%m-%d %H:%M:%S'), numbercall, job['id']))
|
|
|
|
cr.commit()
|
|
|
|
|
2009-10-02 16:00:07 +00:00
|
|
|
|
2010-12-23 11:28:24 +00:00
|
|
|
cr.execute('select min(nextcall) as min_next_call from ir_cron where numbercall<>0 and active')
|
2010-09-06 15:29:27 +00:00
|
|
|
next_call = cr.dictfetchone()['min_next_call']
|
2009-10-08 19:50:28 +00:00
|
|
|
if next_call:
|
2009-10-02 16:00:07 +00:00
|
|
|
next_call = time.mktime(time.strptime(next_call, '%Y-%m-%d %H:%M:%S'))
|
|
|
|
else:
|
|
|
|
next_call = int(time.time()) + 3600 # if do not find active cron job from database, it will run again after 1 day
|
2010-09-06 15:29:27 +00:00
|
|
|
|
2009-10-02 16:00:07 +00:00
|
|
|
if not check:
|
|
|
|
self.setAlarm(self._poolJobs, next_call, db_name, db_name)
|
2009-10-08 19:50:28 +00:00
|
|
|
|
|
|
|
except Exception, ex:
|
2011-01-17 21:31:08 +00:00
|
|
|
self._logger.warning('Exception in cron:', exc_info=True)
|
2010-09-06 15:29:27 +00:00
|
|
|
|
2009-10-02 16:00:07 +00:00
|
|
|
finally:
|
|
|
|
cr.commit()
|
|
|
|
cr.close()
|
|
|
|
|
2010-09-06 15:29:27 +00:00
|
|
|
def restart(self, dbname):
|
|
|
|
self.cancel(dbname)
|
2010-11-19 09:38:12 +00:00
|
|
|
# Reschedule cron processing job asap, but not in the current thread
|
|
|
|
self.setAlarm(self._poolJobs, time.time(), dbname, dbname)
|
2010-09-06 15:29:27 +00:00
|
|
|
|
2011-02-16 14:22:42 +00:00
|
|
|
def update_running_cron(self, cr):
|
|
|
|
# Verify whether the server is already started and thus whether we need to commit
|
|
|
|
# immediately our changes and restart the cron agent in order to apply the change
|
|
|
|
# immediately. The commit() is needed because as soon as the cron is (re)started it
|
|
|
|
# will query the database with its own cursor, possibly before the end of the
|
|
|
|
# current transaction.
|
|
|
|
# This commit() is not an issue in most cases, but we must absolutely avoid it
|
|
|
|
# when the server is only starting or loading modules (hence the test on pool._init).
|
|
|
|
if not self.pool._init:
|
|
|
|
cr.commit()
|
|
|
|
self.restart(cr.dbname)
|
|
|
|
|
2009-09-16 09:20:36 +00:00
|
|
|
def create(self, cr, uid, vals, context=None):
|
2010-09-06 15:29:27 +00:00
|
|
|
res = super(ir_cron, self).create(cr, uid, vals, context=context)
|
2011-02-16 14:22:42 +00:00
|
|
|
self.update_running_cron(cr)
|
2009-09-16 09:20:36 +00:00
|
|
|
return res
|
2010-09-06 15:29:27 +00:00
|
|
|
|
2009-09-16 09:20:36 +00:00
|
|
|
def write(self, cr, user, ids, vals, context=None):
|
|
|
|
res = super(ir_cron, self).write(cr, user, ids, vals, context=context)
|
2011-02-16 14:22:42 +00:00
|
|
|
self.update_running_cron(cr)
|
2009-09-16 09:20:36 +00:00
|
|
|
return res
|
2010-09-06 15:29:27 +00:00
|
|
|
|
2009-09-16 09:20:36 +00:00
|
|
|
def unlink(self, cr, uid, ids, context=None):
|
|
|
|
res = super(ir_cron, self).unlink(cr, uid, ids, context=context)
|
2011-02-16 14:22:42 +00:00
|
|
|
self.update_running_cron(cr)
|
2009-09-16 09:20:36 +00:00
|
|
|
return res
|
2006-12-07 13:41:40 +00:00
|
|
|
ir_cron()
|
2008-07-23 15:01:27 +00:00
|
|
|
|
|
|
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
|
|
|