job.rb 6.3 KB

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