1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
21 from persistent
import Persistent
22 from flask
import render_template
24 from indico
.util
.fossilize
import fossilizes
, Fossilizable
25 from indico
.util
.date_time
import int_timestamp
, format_datetime
26 from indico
.modules
.scheduler
.fossils
import ITaskFossil
27 from indico
.modules
.scheduler
import base
28 from indico
.modules
.users
.legacy
import AvatarUserWrapper
29 from indico
.core
.db
import db
30 from indico
.core
.db
.sqlalchemy
.util
.session
import update_session_options
31 from indico
.core
.index
import IUniqueIdProvider
, IIndexableByArbitraryDateTime
32 from indico
.core
.config
import Config
33 from indico
.web
.flask
.util
import url_for
34 from indico
.util
.i18n
import _
37 # Defines base classes for tasks, and some specific tasks as well
40 class TimedEvent(Persistent
, Fossilizable
):
42 zope
.interface
.implements(IUniqueIdProvider
,
43 IIndexableByArbitraryDateTime
)
45 def getIndexingDateTime(self
):
46 return int_timestamp(self
._getCurrentDateTime
())
48 def _getCurrentDateTime(self
):
49 # just get current date/time
50 return base
.TimeSource
.get().getCurrentTime()
52 def __conform__(self
, proto
):
54 if proto
== IIndexableByArbitraryDateTime
:
55 return self
.getIndexingDateTime()
60 class BaseTask(TimedEvent
):
62 A base class for tasks.
63 `expiryDate` is the last point in time when the task can run. A task will
64 refuse to run if current time is past `expiryDate`
67 DISABLE_ZODB_HOOK
= False
69 fossilizes(ITaskFossil
)
71 # seconds to consider a task AWOL
74 def __init__(self
, expiryDate
=None):
75 self
.createdOn
= self
._getCurrentDateTime
()
76 self
.expiryDate
= expiryDate
77 self
.typeId
= self
.__class
__.__name
__
85 def __cmp__(self
, obj
):
86 from indico
.modules
.scheduler
.tasks
.periodic
import TaskOccurrence
89 elif isinstance(obj
, TaskOccurrence
):
90 task_cmp
= cmp(self
, obj
.getTask())
95 # This condition will mostlike never happen
96 elif not isinstance(obj
, BaseTask
) or (self
.id == obj
.id and self
.id is None):
97 return cmp(hash(self
), hash(obj
))
98 # This is the 'default case' where we are comparing 2 Tasks
100 return cmp(self
.id, obj
.id)
103 '''Resets a task to its state before being run'''
106 self
.onRunningListSince
= None
110 def getCreatedOn(self
):
111 return self
.createdOn
113 def getEndedOn(self
):
116 def setEndedOn(self
, dateTime
):
117 self
.endedOn
= dateTime
119 def getStartedOn(self
):
120 return self
.startedOn
122 def setStartedOn(self
, dateTime
):
123 self
.startedOn
= dateTime
125 def setOnRunningListSince(self
, sometime
):
126 self
.onRunningListSince
= sometime
129 def getOnRunningListSince(self
):
130 return self
.onRunningListSince
132 def setStatus(self
, newstatus
):
133 self
.getLogger().info("%s set status %s" % (self
, base
.status(newstatus
)))
134 self
.status
= newstatus
142 def getUniqueId(self
):
143 return "task%s" % self
.id
148 def initialize(self
, newid
, newstatus
):
150 self
.setStatus(newstatus
)
152 def plugLogger(self
, logger
):
153 self
._v
_logger
= logger
156 if not hasattr(self
, '_v_logger') or not self
._v
_logger
:
157 self
._v
_logger
= logging
.getLogger('task/%s' % self
.typeId
)
158 return self
._v
_logger
162 This information will be saved regardless of the task being repeated or not
165 curTime
= self
._getCurrentDateTime
()
166 tsDiff
= int_timestamp(self
.getStartOn()) - int_timestamp(curTime
)
169 self
.getLogger().debug('Task %s will wait for some time. (%s) > (%s)' % \
170 (self
.id, self
.getStartOn(), curTime
))
171 base
.TimeSource
.get().sleep(tsDiff
)
173 if self
.expiryDate
and curTime
> self
.expiryDate
:
174 self
.getLogger().warning(
175 'Task %s will not be executed, expiryDate (%s) < current time (%s)' % \
176 (self
.id, self
.expiryDate
, curTime
))
179 self
.startedOn
= curTime
181 self
.setStatus(base
.TASK_STATUS_RUNNING
)
183 def start(self
, delay
):
184 if self
.DISABLE_ZODB_HOOK
:
185 update_session_options(db
)
186 self
._executionDelay
= delay
189 self
.endedOn
= self
._getCurrentDateTime
()
195 If a task needs to do something once it has run and been removed
196 from runningList, overload this method
201 return "[%s:%s|%s]" % (self
.typeId
, self
.id,
202 base
.status(self
.status
))
205 class OneShotTask(BaseTask
):
207 Tasks that are executed only once
210 def __init__(self
, startDateTime
, expiryDate
= None):
211 super(OneShotTask
, self
).__init
__(expiryDate
= expiryDate
)
212 self
.startDateTime
= startDateTime
214 def getStartOn(self
):
215 return self
.startDateTime
217 def setStartOn(self
, newtime
):
218 self
.startDateTime
= newtime
221 self
.setStatus(base
.TASK_STATUS_TERMINATED
)
222 self
.setEndedOn(self
._getCurrentDateTime
())
225 class SendMailTask(OneShotTask
):
228 def __init__(self
, startDateTime
):
229 super(SendMailTask
, self
).__init
__(startDateTime
)
236 self
.smtpServer
= Config
.getInstance().getSmtpServer()
238 def _prepare(self
, check
):
240 Overloaded by descendants
243 def run(self
, check
=True):
245 from MaKaC
.webinterface
.mail
import GenericMailer
, GenericNotification
248 send
= self
._prepare
(check
=check
)
250 # _prepare decided we shouldn't send the mail?
254 # just in case some ill-behaved code generates empty addresses
255 addrs
= list(smtplib
.quoteaddr(x
) for x
in self
.toAddr
if x
)
256 ccaddrs
= list(smtplib
.quoteaddr(x
) for x
in self
.ccAddr
if x
)
258 if len(addrs
) + len(ccaddrs
) == 0:
259 self
.getLogger().warning("Attention: no recipients, mail won't be sent")
261 self
.getLogger().info("Sending mail To: %s, CC: %s" % (addrs
, ccaddrs
))
263 for user
in self
.toUser
:
264 addrs
.append(smtplib
.quoteaddr(user
.getEmail()))
267 GenericMailer
.send(GenericNotification({"fromAddr": self
.fromAddr
,
270 "subject": self
.subject
,
271 "body": self
.text
}))
273 def setFromAddr(self
, addr
):
277 def getFromAddr(self
):
280 def addToAddr(self
, addr
):
281 if not addr
in self
.toAddr
:
282 self
.toAddr
.append(addr
)
285 def addCcAddr(self
, addr
):
286 if not addr
in self
.ccAddr
:
287 self
.ccAddr
.append(addr
)
290 def removeToAddr(self
, addr
):
291 if addr
in self
.toAddr
:
292 self
.toAddr
.remove(addr
)
295 def setToAddrList(self
, addrList
):
296 """Params: addrList -- addresses of type : list of str."""
297 self
.toAddr
= addrList
300 def getToAddrList(self
):
303 def setCcAddrList(self
, addrList
):
304 """Params: addrList -- addresses of type : list of str."""
305 self
.ccAddr
= addrList
308 def getCcAddrList(self
):
311 def addToUser(self
, user
):
312 if not user
in self
.toUser
:
313 self
.toUser
.append(user
)
316 def removeToUser(self
, user
):
317 if user
in self
.toUser
:
318 self
.toUser
.remove(user
)
321 def getToUserList(self
):
324 def setSubject(self
, subject
):
325 self
.subject
= subject
327 def getSubject(self
):
330 def setText(self
, text
):
337 class HTTPTask(OneShotTask
):
338 def __init__(self
, url
, data
=None):
339 super(HTTPTask
, self
).__init
__(base
.TimeSource
.get().getCurrentTime())
344 method
= 'POST' if self
._data
is not None else 'GET'
345 self
.getLogger().info('Executing HTTP task: %s %s' % (method
, self
._url
))
346 data
= urllib
.urlencode(self
._data
) if self
._data
is not None else None
347 req
= urllib
.urlopen(self
._url
, data
)
351 class SampleOneShotTask(OneShotTask
):
353 self
.getLogger().debug('Now i shall sleeeeeeeep!')
354 base
.TimeSource
.get().sleep(1)
355 self
.getLogger().debug('%s executed' % self
.__class
__.__name
__)
358 class DeletedTask(BaseTask
):
359 """ZODB migration class to avoid broken objects for deleted tasks"""
361 def getStartOn(self
):
363 return self
._nextOccurrence
364 except AttributeError:
365 return self
.startDateTime