123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
- class Job < ApplicationModel
- include ChecksClientNotification
- include ChecksConditionValidation
- include Job::Assets
- store :timeplan
- store :condition
- store :perform
- validates :name, presence: true
- before_create :updated_matching, :update_next_run_at
- before_update :updated_matching, :update_next_run_at
- =begin
- verify each job if needed to run (e. g. if true and times are matching) and execute it
- Job.run
- =end
- def self.run
- start_at = Time.zone.now
- jobs = Job.where(active: true)
- jobs.each do |job|
- next if !job.executable?
- 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
- force to run job (ignore times are matching)
- job.run(true)
- =end
- def run(force = false, start_at = Time.zone.now)
- logger.debug { "Execute job #{inspect}" }
- if !executable?(start_at) && force == false
- if next_run_at && next_run_at <= Time.zone.now
- save!
- end
- return
- end
- matching = matching_count
- if self.matching != matching
- self.matching = matching
- save!
- end
- 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
- ticket_count, tickets = Ticket.selectors(condition, 2_000)
- logger.debug { "Job #{name} with #{ticket_count} tickets" }
- self.processed = ticket_count || 0
- self.running = true
- self.last_run_at = Time.zone.now
- save!
- tickets&.each do |ticket|
- Transaction.execute(disable_notification: disable_notification, reset_user_id: true) do
- ticket.perform_changes(perform, 'job')
- end
- end
- self.running = false
- self.last_run_at = Time.zone.now
- save!
- end
- def executable?(start_at = Time.zone.now)
- return false if !active
- # only execute jobs, older then 1 min, to give admin posibility to change
- return false if updated_at > Time.zone.now - 1.minute
- # check if job got stuck
- return false if running == true && last_run_at && Time.zone.now - 1.day < last_run_at
- # check if jobs need to be executed
- # ignore if job was running within last 10 min.
- return false if last_run_at && last_run_at > start_at - 10.minutes
- true
- end
- def in_timeplan?(time = Time.zone.now)
- 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
- ticket_count, tickets = Ticket.selectors(condition, 1)
- ticket_count || 0
- end
- def next_run_at_calculate(time = Time.zone.now)
- if last_run_at
- diff = time - last_run_at
- if diff.positive?
- 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
- day_to_check = if day_counter.zero?
- 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
- if min < 9
- min = 0
- elsif min < 20
- min = 10
- elsif min < 30
- min = 20
- elsif min < 40
- min = 30
- elsif min < 50
- min = 40
- elsif min < 60
- min = 50
- end
- # 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
- if day_to_check.min.nonzero?
- (0..5).each do |minute_counter|
- if minute_counter.nonzero?
- break if day_to_check.min.zero?
- 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]
- 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]
- time_to_check = minute_to_check
- break
- end
- next if !minute_to_check
- return time_to_check
- end
- end
- nil
- end
- private
- def updated_matching
- self.matching = matching_count
- true
- end
- def update_next_run_at
- self.next_run_at = next_run_at_calculate
- true
- end
- def match_minutes(minutes)
- return 0 if minutes < 10
- "#{minutes.to_s.gsub(/(\d)\d/, '\\1')}0".to_i
- end
- end
|