job.rb 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class Job < ApplicationModel
  3. include ChecksClientNotification
  4. include ChecksConditionValidation
  5. include Job::Assets
  6. store :timeplan
  7. store :condition
  8. store :perform
  9. validates :name, presence: true
  10. before_create :updated_matching, :update_next_run_at
  11. before_update :updated_matching, :update_next_run_at
  12. =begin
  13. verify each job if needed to run (e. g. if true and times are matching) and execute it
  14. Job.run
  15. =end
  16. def self.run
  17. start_at = Time.zone.now
  18. jobs = Job.where(active: true)
  19. jobs.each do |job|
  20. next if !job.executable?
  21. job.run(false, start_at)
  22. end
  23. true
  24. end
  25. =begin
  26. execute a single job if needed (e. g. if true and times are matching)
  27. job = Job.find(123)
  28. job.run
  29. force to run job (ignore times are matching)
  30. job.run(true)
  31. =end
  32. def run(force = false, start_at = Time.zone.now)
  33. logger.debug { "Execute job #{inspect}" }
  34. if !executable?(start_at) && force == false
  35. if next_run_at && next_run_at <= Time.zone.now
  36. save!
  37. end
  38. return
  39. end
  40. matching = matching_count
  41. if self.matching != matching
  42. self.matching = matching
  43. save!
  44. end
  45. if !in_timeplan?(start_at) && force == false
  46. if next_run_at && next_run_at <= Time.zone.now
  47. save!
  48. end
  49. return
  50. end
  51. # find tickets to change
  52. ticket_count, tickets = Ticket.selectors(condition, 2_000)
  53. logger.debug { "Job #{name} with #{ticket_count} tickets" }
  54. self.processed = ticket_count || 0
  55. self.running = true
  56. self.last_run_at = Time.zone.now
  57. save!
  58. tickets&.each do |ticket|
  59. Transaction.execute(disable_notification: disable_notification, reset_user_id: true) do
  60. ticket.perform_changes(perform, 'job')
  61. end
  62. end
  63. self.running = false
  64. self.last_run_at = Time.zone.now
  65. save!
  66. end
  67. def executable?(start_at = Time.zone.now)
  68. return false if !active
  69. # only execute jobs, older then 1 min, to give admin posibility to change
  70. return false if updated_at > Time.zone.now - 1.minute
  71. # check if job got stuck
  72. return false if running == true && last_run_at && Time.zone.now - 1.day < last_run_at
  73. # check if jobs need to be executed
  74. # ignore if job was running within last 10 min.
  75. return false if last_run_at && last_run_at > start_at - 10.minutes
  76. true
  77. end
  78. def in_timeplan?(time = Time.zone.now)
  79. day_map = {
  80. 0 => 'Sun',
  81. 1 => 'Mon',
  82. 2 => 'Tue',
  83. 3 => 'Wed',
  84. 4 => 'Thu',
  85. 5 => 'Fri',
  86. 6 => 'Sat',
  87. }
  88. # check day
  89. return false if !timeplan['days']
  90. return false if !timeplan['days'][day_map[time.wday]]
  91. # check hour
  92. return false if !timeplan['hours']
  93. return false if !timeplan['hours'][time.hour.to_s] && !timeplan['hours'][time.hour]
  94. # check min
  95. return false if !timeplan['minutes']
  96. return false if !timeplan['minutes'][match_minutes(time.min).to_s] && !timeplan['minutes'][match_minutes(time.min)]
  97. true
  98. end
  99. def matching_count
  100. ticket_count, tickets = Ticket.selectors(condition, 1)
  101. ticket_count || 0
  102. end
  103. def next_run_at_calculate(time = Time.zone.now)
  104. if last_run_at
  105. diff = time - last_run_at
  106. if diff.positive?
  107. time = time + 10.minutes
  108. end
  109. end
  110. day_map = {
  111. 0 => 'Sun',
  112. 1 => 'Mon',
  113. 2 => 'Tue',
  114. 3 => 'Wed',
  115. 4 => 'Thu',
  116. 5 => 'Fri',
  117. 6 => 'Sat',
  118. }
  119. return nil if !active
  120. return nil if !timeplan['days']
  121. return nil if !timeplan['hours']
  122. return nil if !timeplan['minutes']
  123. # loop week days
  124. (0..7).each do |day_counter|
  125. time_to_check = nil
  126. day_to_check = if day_counter.zero?
  127. time
  128. else
  129. time + 1.day
  130. end
  131. if !timeplan['days'][day_map[day_to_check.wday]]
  132. # start on next day at 00:00:00
  133. time = day_to_check - day_to_check.sec.seconds
  134. time = time - day_to_check.min.minutes
  135. time = time - day_to_check.hour.hours
  136. next
  137. end
  138. min = day_to_check.min
  139. if min < 9
  140. min = 0
  141. elsif min < 20
  142. min = 10
  143. elsif min < 30
  144. min = 20
  145. elsif min < 40
  146. min = 30
  147. elsif min < 50
  148. min = 40
  149. elsif min < 60
  150. min = 50
  151. end
  152. # move to [0-5]0:00 time stamps
  153. day_to_check = day_to_check - day_to_check.min.minutes + min.minutes
  154. day_to_check = day_to_check - day_to_check.sec.seconds
  155. # loop minutes till next full hour
  156. if day_to_check.min.nonzero?
  157. (0..5).each do |minute_counter|
  158. if minute_counter.nonzero?
  159. break if day_to_check.min.zero?
  160. day_to_check = day_to_check + 10.minutes
  161. end
  162. next if !timeplan['hours'][day_to_check.hour] && !timeplan['hours'][day_to_check.hour.to_s]
  163. next if !timeplan['minutes'][match_minutes(day_to_check.min)] && !timeplan['minutes'][match_minutes(day_to_check.min).to_s]
  164. return day_to_check
  165. end
  166. end
  167. # loop hours
  168. hour_to_check = nil
  169. (0..23).each do |hour_counter|
  170. hour_to_check = day_to_check + hour_counter.hours
  171. # start on next day
  172. if hour_to_check.day != day_to_check.day
  173. time = day_to_check - day_to_check.hour.hours
  174. break
  175. end
  176. # ignore not configured hours
  177. next if !timeplan['hours'][hour_to_check.hour] && !timeplan['hours'][hour_to_check.hour.to_s]
  178. return nil if !hour_to_check
  179. # loop minutes
  180. minute_to_check = nil
  181. (0..5).each do |minute_counter|
  182. minute_to_check = hour_to_check + minute_counter.minutes * 10
  183. next if !timeplan['minutes'][match_minutes(minute_to_check.min)] && !timeplan['minutes'][match_minutes(minute_to_check.min).to_s]
  184. time_to_check = minute_to_check
  185. break
  186. end
  187. next if !minute_to_check
  188. return time_to_check
  189. end
  190. end
  191. nil
  192. end
  193. private
  194. def updated_matching
  195. self.matching = matching_count
  196. true
  197. end
  198. def update_next_run_at
  199. self.next_run_at = next_run_at_calculate
  200. true
  201. end
  202. def match_minutes(minutes)
  203. return 0 if minutes < 10
  204. "#{minutes.to_s.gsub(/(\d)\d/, '\\1')}0".to_i
  205. end
  206. end