##############################################################################
#
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
#					Fabien Pinckaers <fp@tiny.Be>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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 General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################
#
# SPEC: Execute "model.function(*eval(args))" periodically
#	date		: date to execute the job or NULL if directly
#	delete_after: delete the ir.cron entry after execution
#	interval_*  : period
#	max_repeat  : number of execution or NULL if endlessly
#
# TODO:
#	Error treatment: exception, request, ... -> send request to uid
#

from mx import DateTime
import time
import netsvc
import tools
import sql_db
from osv import fields,osv

next_wait = 60

_intervalTypes = {
	'work_days': lambda interal: DateTime.RelativeDateTime(days=interval),
	'days': lambda interval: DateTime.RelativeDateTime(days=interval),
	'hours': lambda interval: DateTime.RelativeDateTime(hours=interval),
	'weeks': lambda interval: DateTime.RelativeDateTime(days=7*interval),
	'months': lambda interval: DateTime.RelativeDateTime(months=interval),
	'minutes': lambda interval: DateTime.RelativeDateTime(minutes=interval),
}

class ir_cron(osv.osv, netsvc.Agent):
	_name = "ir.cron"
	_columns = {
		'name': fields.char('Name', size=60, required=True),
		'user_id': fields.many2one('res.users', 'User', required=True),
		'active': fields.boolean('Active'),
		'interval_number': fields.integer('Interval Number'),
		'interval_type': fields.selection( [('minutes', 'Minutes'),
		    ('hours', 'Hours'), ('days', 'Days'),('weeks', 'Weeks'), ('months', 'Months')], 'Interval Unit'),
		# number of time the function is called, a negative number
		# indicates that the function will always be called.
		'numbercall': fields.integer('Number of calls'),
		
		# Repeat missed cronjobs ?
		'doall' : fields.boolean('Repeat all missed'),

		'nextcall' : fields.datetime('Next call date', required=True),
		'model': fields.char('Model', size=64),
		'function': fields.char('Function', size=64),
		'args': fields.text('Arguments'),

		# 0 = Very Urgent, 10 = not urgent
		'priority': fields.integer('Priority (0=Very Urgent)')
	}

	_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
	}

	def _callback(self, cr, uid, model, func, args):
		args = (args or []) and eval(args)
		f = getattr(self.pool.get(model), func)
		f(cr, uid, *args)

	def _poolJobs(self, check=False):
		now = DateTime.now()
		cr = sql_db.db.cursor()
		try:
			cr.execute('select * from ir_cron where numbercall<>0 and active and nextcall<=now() order by priority')
			for job in cr.dictfetchall():
				nextcall = DateTime.strptime(job['nextcall'], '%Y-%m-%d %H:%M:%S')
				numbercall = job['numbercall']
				
				ok = False
				while nextcall<now and numbercall:
					if numbercall > 0:
						numbercall -= 1
					if not ok or job['doall']:
						self._callback(cr, job['user_id'], job['model'], job['function'], job['args'])
					if numbercall:
						nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
					ok = True
				addsql=''
				if not numbercall:
					addsql = ', active=False'
				cr.execute("update ir_cron set nextcall=%s, numbercall=%d"+addsql+" where id=%d",
				  (nextcall.strftime('%Y-%m-%d %H:%M:%S'), numbercall, job['id']))
				cr.commit()
		finally:
			cr.close()
		#
		# Can be improved to do at the min(min(nextcalls), time()+next_wait)
		# But is this an improvement ?
		# 
		if not check:
			self.setAlarm(self._poolJobs, int(time.time())+next_wait)

	def check(self):
		self._poolJobs(True)

	def __init__(self):
		super(ir_cron, self).__init__()
ir_cron()
