2016-10-19 03:11:36 +00:00
|
|
|
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
2014-12-25 23:41:00 +00:00
|
|
|
|
|
|
|
class Job < ApplicationModel
|
2017-05-02 15:21:13 +00:00
|
|
|
include ChecksClientNotification
|
|
|
|
include ChecksConditionValidation
|
2019-01-29 14:04:47 +00:00
|
|
|
include ChecksPerformValidation
|
2017-01-31 17:13:45 +00:00
|
|
|
|
2016-03-20 19:09:52 +00:00
|
|
|
include Job::Assets
|
|
|
|
|
2014-12-25 23:41:00 +00:00
|
|
|
store :timeplan
|
|
|
|
store :condition
|
2016-03-18 02:04:49 +00:00
|
|
|
store :perform
|
2015-04-27 13:42:53 +00:00
|
|
|
validates :name, presence: true
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
before_create :updated_matching, :update_next_run_at
|
|
|
|
before_update :updated_matching, :update_next_run_at
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
verify each job if needed to run (e. g. if true and times are matching) and execute it
|
|
|
|
|
|
|
|
Job.run
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2014-12-25 23:41:00 +00:00
|
|
|
def self.run
|
2017-11-17 10:39:57 +00:00
|
|
|
start_at = Time.zone.now
|
2018-08-16 13:12:55 +00:00
|
|
|
jobs = Job.where(active: true)
|
2014-12-25 23:41:00 +00:00
|
|
|
jobs.each do |job|
|
2018-08-16 13:12:55 +00:00
|
|
|
next if !job.executable?
|
2018-10-09 06:17:41 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
job.run(false, start_at)
|
|
|
|
end
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
|
|
|
execute a single job if needed (e. g. if true and times are matching)
|
|
|
|
|
|
|
|
job = Job.find(123)
|
|
|
|
|
|
|
|
job.run
|
2016-03-18 02:04:49 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
force to run job (ignore times are matching)
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
job.run(true)
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def run(force = false, start_at = Time.zone.now)
|
2018-03-20 17:47:49 +00:00
|
|
|
logger.debug { "Execute job #{inspect}" }
|
2017-11-17 10:39:57 +00:00
|
|
|
|
|
|
|
if !executable?(start_at) && force == false
|
|
|
|
if next_run_at && next_run_at <= Time.zone.now
|
|
|
|
save!
|
2016-03-18 02:04:49 +00:00
|
|
|
end
|
2017-11-17 10:39:57 +00:00
|
|
|
return
|
|
|
|
end
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
matching = matching_count
|
|
|
|
if self.matching != matching
|
|
|
|
self.matching = matching
|
|
|
|
save!
|
|
|
|
end
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
if !in_timeplan?(start_at) && force == false
|
|
|
|
if next_run_at && next_run_at <= Time.zone.now
|
|
|
|
save!
|
|
|
|
end
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
# find tickets to change
|
2019-03-26 00:17:17 +00:00
|
|
|
ticket_count, tickets = Ticket.selectors(condition, limit: 2_000)
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2018-03-20 17:47:49 +00:00
|
|
|
logger.debug { "Job #{name} with #{ticket_count} tickets" }
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
self.processed = ticket_count || 0
|
|
|
|
self.running = true
|
2018-08-16 13:12:55 +00:00
|
|
|
self.last_run_at = Time.zone.now
|
2017-11-17 10:39:57 +00:00
|
|
|
save!
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2017-11-23 08:09:44 +00:00
|
|
|
tickets&.each do |ticket|
|
|
|
|
Transaction.execute(disable_notification: disable_notification, reset_user_id: true) do
|
|
|
|
ticket.perform_changes(perform, 'job')
|
2014-12-25 23:41:00 +00:00
|
|
|
end
|
|
|
|
end
|
2017-11-17 10:39:57 +00:00
|
|
|
|
|
|
|
self.running = false
|
|
|
|
self.last_run_at = Time.zone.now
|
|
|
|
save!
|
2014-12-25 23:41:00 +00:00
|
|
|
end
|
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
def executable?(start_at = Time.zone.now)
|
2016-03-18 14:29:03 +00:00
|
|
|
return false if !active
|
2016-03-18 02:04:49 +00:00
|
|
|
|
|
|
|
# only execute jobs, older then 1 min, to give admin posibility to change
|
|
|
|
return false if updated_at > Time.zone.now - 1.minute
|
|
|
|
|
2018-08-16 13:12:55 +00:00
|
|
|
# check if job got stuck
|
|
|
|
return false if running == true && last_run_at && Time.zone.now - 1.day < last_run_at
|
|
|
|
|
2016-03-18 02:04:49 +00:00
|
|
|
# check if jobs need to be executed
|
|
|
|
# ignore if job was running within last 10 min.
|
2017-11-17 10:39:57 +00:00
|
|
|
return false if last_run_at && last_run_at > start_at - 10.minutes
|
2016-03-18 02:04:49 +00:00
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
def in_timeplan?(time = Time.zone.now)
|
2016-03-18 02:04:49 +00:00
|
|
|
day_map = {
|
|
|
|
0 => 'Sun',
|
|
|
|
1 => 'Mon',
|
|
|
|
2 => 'Tue',
|
|
|
|
3 => 'Wed',
|
|
|
|
4 => 'Thu',
|
|
|
|
5 => 'Fri',
|
|
|
|
6 => 'Sat',
|
|
|
|
}
|
|
|
|
|
|
|
|
# check day
|
|
|
|
return false if !timeplan['days']
|
|
|
|
return false if !timeplan['days'][day_map[time.wday]]
|
|
|
|
|
|
|
|
# check hour
|
|
|
|
return false if !timeplan['hours']
|
|
|
|
return false if !timeplan['hours'][time.hour.to_s] && !timeplan['hours'][time.hour]
|
|
|
|
|
|
|
|
# check min
|
|
|
|
return false if !timeplan['minutes']
|
|
|
|
return false if !timeplan['minutes'][match_minutes(time.min).to_s] && !timeplan['minutes'][match_minutes(time.min)]
|
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def matching_count
|
2019-03-26 00:17:17 +00:00
|
|
|
ticket_count, tickets = Ticket.selectors(condition, limit: 1)
|
2016-03-18 02:04:49 +00:00
|
|
|
ticket_count || 0
|
|
|
|
end
|
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
def next_run_at_calculate(time = Time.zone.now)
|
|
|
|
if last_run_at
|
|
|
|
diff = time - last_run_at
|
2016-10-24 21:59:18 +00:00
|
|
|
if diff.positive?
|
2016-03-18 14:29:03 +00:00
|
|
|
time = time + 10.minutes
|
|
|
|
end
|
|
|
|
end
|
|
|
|
day_map = {
|
|
|
|
0 => 'Sun',
|
|
|
|
1 => 'Mon',
|
|
|
|
2 => 'Tue',
|
|
|
|
3 => 'Wed',
|
|
|
|
4 => 'Thu',
|
|
|
|
5 => 'Fri',
|
|
|
|
6 => 'Sat',
|
|
|
|
}
|
|
|
|
return nil if !active
|
|
|
|
return nil if !timeplan['days']
|
|
|
|
return nil if !timeplan['hours']
|
|
|
|
return nil if !timeplan['minutes']
|
|
|
|
|
|
|
|
# loop week days
|
|
|
|
(0..7).each do |day_counter|
|
|
|
|
time_to_check = nil
|
2016-07-26 22:02:28 +00:00
|
|
|
day_to_check = if day_counter.zero?
|
2016-03-18 14:29:03 +00:00
|
|
|
time
|
|
|
|
else
|
|
|
|
time + 1.day
|
|
|
|
end
|
|
|
|
if !timeplan['days'][day_map[day_to_check.wday]]
|
|
|
|
|
|
|
|
# start on next day at 00:00:00
|
|
|
|
time = day_to_check - day_to_check.sec.seconds
|
|
|
|
time = time - day_to_check.min.minutes
|
|
|
|
time = time - day_to_check.hour.hours
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
min = day_to_check.min
|
2019-02-26 14:49:05 +00:00
|
|
|
min = if min < 10
|
|
|
|
0
|
|
|
|
elsif min < 20
|
|
|
|
10
|
|
|
|
elsif min < 30
|
|
|
|
20
|
|
|
|
elsif min < 40
|
|
|
|
30
|
|
|
|
elsif min < 50
|
|
|
|
40
|
|
|
|
else
|
|
|
|
50
|
|
|
|
end
|
2016-03-18 14:29:03 +00:00
|
|
|
|
|
|
|
# move to [0-5]0:00 time stamps
|
|
|
|
day_to_check = day_to_check - day_to_check.min.minutes + min.minutes
|
|
|
|
day_to_check = day_to_check - day_to_check.sec.seconds
|
|
|
|
|
|
|
|
# loop minutes till next full hour
|
2016-07-26 22:02:28 +00:00
|
|
|
if day_to_check.min.nonzero?
|
2016-03-18 14:29:03 +00:00
|
|
|
(0..5).each do |minute_counter|
|
2016-07-26 22:02:28 +00:00
|
|
|
if minute_counter.nonzero?
|
|
|
|
break if day_to_check.min.zero?
|
2018-10-09 06:17:41 +00:00
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
day_to_check = day_to_check + 10.minutes
|
|
|
|
end
|
|
|
|
next if !timeplan['hours'][day_to_check.hour] && !timeplan['hours'][day_to_check.hour.to_s]
|
|
|
|
next if !timeplan['minutes'][match_minutes(day_to_check.min)] && !timeplan['minutes'][match_minutes(day_to_check.min).to_s]
|
2018-10-09 06:17:41 +00:00
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
return day_to_check
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# loop hours
|
|
|
|
hour_to_check = nil
|
|
|
|
(0..23).each do |hour_counter|
|
|
|
|
hour_to_check = day_to_check + hour_counter.hours
|
|
|
|
|
|
|
|
# start on next day
|
|
|
|
if hour_to_check.day != day_to_check.day
|
|
|
|
time = day_to_check - day_to_check.hour.hours
|
|
|
|
break
|
|
|
|
end
|
|
|
|
|
|
|
|
# ignore not configured hours
|
|
|
|
next if !timeplan['hours'][hour_to_check.hour] && !timeplan['hours'][hour_to_check.hour.to_s]
|
|
|
|
return nil if !hour_to_check
|
|
|
|
|
|
|
|
# loop minutes
|
|
|
|
minute_to_check = nil
|
|
|
|
(0..5).each do |minute_counter|
|
|
|
|
minute_to_check = hour_to_check + minute_counter.minutes * 10
|
|
|
|
next if !timeplan['minutes'][match_minutes(minute_to_check.min)] && !timeplan['minutes'][match_minutes(minute_to_check.min).to_s]
|
2018-10-09 06:17:41 +00:00
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
time_to_check = minute_to_check
|
|
|
|
break
|
|
|
|
end
|
|
|
|
next if !minute_to_check
|
2018-10-09 06:17:41 +00:00
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
return time_to_check
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2014-12-25 23:41:00 +00:00
|
|
|
private
|
|
|
|
|
|
|
|
def updated_matching
|
2016-03-18 02:04:49 +00:00
|
|
|
self.matching = matching_count
|
2017-06-16 22:53:20 +00:00
|
|
|
true
|
2014-12-25 23:41:00 +00:00
|
|
|
end
|
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
def update_next_run_at
|
|
|
|
self.next_run_at = next_run_at_calculate
|
2017-06-16 22:53:20 +00:00
|
|
|
true
|
2016-03-18 14:29:03 +00:00
|
|
|
end
|
|
|
|
|
2016-03-18 02:04:49 +00:00
|
|
|
def match_minutes(minutes)
|
|
|
|
return 0 if minutes < 10
|
2018-10-09 06:17:41 +00:00
|
|
|
|
2016-03-18 02:04:49 +00:00
|
|
|
"#{minutes.to_s.gsub(/(\d)\d/, '\\1')}0".to_i
|
2014-12-25 23:41:00 +00:00
|
|
|
end
|
2016-03-18 02:04:49 +00:00
|
|
|
|
2015-04-27 14:15:29 +00:00
|
|
|
end
|