resque-mongo-scheduler 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +47 -0
- data/HISTORY.md +88 -0
- data/LICENSE +21 -0
- data/README.markdown +316 -0
- data/Rakefile +13 -0
- data/lib/resque/scheduler.rb +244 -0
- data/lib/resque_scheduler/search_delayed.rb +49 -0
- data/lib/resque_scheduler/server/views/delayed.erb +64 -0
- data/lib/resque_scheduler/server/views/delayed_timestamp.erb +26 -0
- data/lib/resque_scheduler/server/views/scheduler.erb +39 -0
- data/lib/resque_scheduler/server.rb +58 -0
- data/lib/resque_scheduler/tasks.rb +25 -0
- data/lib/resque_scheduler/version.rb +3 -0
- data/lib/resque_scheduler.rb +238 -0
- data/resque-mongo-scheduler.gemspec +29 -0
- data/tasks/resque_scheduler.rake +2 -0
- data/test/delayed_queue_test.rb +234 -0
- data/test/redis-test.conf +115 -0
- data/test/resque-web_test.rb +31 -0
- data/test/scheduler_args_test.rb +83 -0
- data/test/scheduler_test.rb +241 -0
- data/test/test_helper.rb +92 -0
- metadata +150 -0
| @@ -0,0 +1,244 @@ | |
| 1 | 
            +
            require 'rufus/scheduler'
         | 
| 2 | 
            +
            require 'thwait'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Resque
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              class Scheduler
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                extend Resque::Helpers
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                class << self
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # If true, logs more stuff...
         | 
| 13 | 
            +
                  attr_accessor :verbose
         | 
| 14 | 
            +
                  
         | 
| 15 | 
            +
                  # If set, produces no output
         | 
| 16 | 
            +
                  attr_accessor :mute
         | 
| 17 | 
            +
                  
         | 
| 18 | 
            +
                  # If set, will try to update the schulde in the loop
         | 
| 19 | 
            +
                  attr_accessor :dynamic
         | 
| 20 | 
            +
                  
         | 
| 21 | 
            +
                  # the Rufus::Scheduler jobs that are scheduled
         | 
| 22 | 
            +
                  def scheduled_jobs
         | 
| 23 | 
            +
                    @@scheduled_jobs
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  # Schedule all jobs and continually look for delayed jobs (never returns)
         | 
| 27 | 
            +
                  def run
         | 
| 28 | 
            +
                    $0 = "resque-mongo-scheduler: Starting"
         | 
| 29 | 
            +
                    # trap signals
         | 
| 30 | 
            +
                    register_signal_handlers
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    # Load the schedule into rufus
         | 
| 33 | 
            +
                    procline "Loading Schedule"
         | 
| 34 | 
            +
                    load_schedule!
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    # Now start the scheduling part of the loop.
         | 
| 37 | 
            +
                    loop do
         | 
| 38 | 
            +
                      handle_delayed_items
         | 
| 39 | 
            +
                      update_schedule if dynamic
         | 
| 40 | 
            +
                      poll_sleep
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    # never gets here.
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  # For all signals, set the shutdown flag and wait for current
         | 
| 47 | 
            +
                  # poll/enqueing to finish (should be almost istant).  In the
         | 
| 48 | 
            +
                  # case of sleeping, exit immediately.
         | 
| 49 | 
            +
                  def register_signal_handlers
         | 
| 50 | 
            +
                    trap("TERM") { shutdown }
         | 
| 51 | 
            +
                    trap("INT") { shutdown }
         | 
| 52 | 
            +
                    
         | 
| 53 | 
            +
                    begin
         | 
| 54 | 
            +
                      trap('QUIT') { shutdown   }
         | 
| 55 | 
            +
                      trap('USR1') { kill_child }
         | 
| 56 | 
            +
                      trap('USR2') { reload_schedule! }
         | 
| 57 | 
            +
                    rescue ArgumentError
         | 
| 58 | 
            +
                      warn "Signals QUIT and USR1 and USR2 not supported."
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  # Pulls the schedule from Resque.schedule and loads it into the
         | 
| 63 | 
            +
                  # rufus scheduler instance
         | 
| 64 | 
            +
                  def load_schedule!
         | 
| 65 | 
            +
                    log! "Schedule empty! Set Resque.schedule" if Resque.schedule.empty?
         | 
| 66 | 
            +
                    
         | 
| 67 | 
            +
                    @@scheduled_jobs = {}
         | 
| 68 | 
            +
                    
         | 
| 69 | 
            +
                    Resque.schedule.each do |name, config|
         | 
| 70 | 
            +
                      load_schedule_job(name, config)
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                    Resque.schedules_changed.remove
         | 
| 73 | 
            +
                    procline "Schedules Loaded"
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                  
         | 
| 76 | 
            +
                  # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
         | 
| 77 | 
            +
                  def load_schedule_job(name, config)
         | 
| 78 | 
            +
                    # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
         | 
| 79 | 
            +
                    # required for the jobs to be scheduled.  If rails_env is missing, the
         | 
| 80 | 
            +
                    # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
         | 
| 81 | 
            +
                    # to.
         | 
| 82 | 
            +
                    if config['rails_env'].nil? || rails_env_matches?(config)
         | 
| 83 | 
            +
                      log! "Scheduling #{name} "
         | 
| 84 | 
            +
                      interval_defined = false
         | 
| 85 | 
            +
                      interval_types = %w{cron every}
         | 
| 86 | 
            +
                      interval_types.each do |interval_type|
         | 
| 87 | 
            +
                        if !config[interval_type].nil? && config[interval_type].length > 0
         | 
| 88 | 
            +
                          begin
         | 
| 89 | 
            +
                            @@scheduled_jobs[name] = rufus_scheduler.send(interval_type, config[interval_type]) do
         | 
| 90 | 
            +
                              log! "queueing #{config['class']} (#{name})"
         | 
| 91 | 
            +
                              enqueue_from_config(config)
         | 
| 92 | 
            +
                            end
         | 
| 93 | 
            +
                          rescue Exception => e
         | 
| 94 | 
            +
                            log! "#{e.class.name}: #{e.message}"
         | 
| 95 | 
            +
                          end
         | 
| 96 | 
            +
                          interval_defined = true
         | 
| 97 | 
            +
                          break
         | 
| 98 | 
            +
                        end
         | 
| 99 | 
            +
                      end
         | 
| 100 | 
            +
                      unless interval_defined
         | 
| 101 | 
            +
                        log! "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  # Returns true if the given schedule config hash matches the current
         | 
| 107 | 
            +
                  # ENV['RAILS_ENV']
         | 
| 108 | 
            +
                  def rails_env_matches?(config)
         | 
| 109 | 
            +
                    config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/,'').split(',').include?(ENV['RAILS_ENV'])
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  # Handles queueing delayed items
         | 
| 113 | 
            +
                  # at_time - Time to start scheduling items (default: now).
         | 
| 114 | 
            +
                  def handle_delayed_items(at_time=nil)
         | 
| 115 | 
            +
                    item = nil
         | 
| 116 | 
            +
                    if timestamp = Resque.next_delayed_timestamp(at_time)
         | 
| 117 | 
            +
                      procline "Processing Delayed Items"
         | 
| 118 | 
            +
                      while !timestamp.nil?
         | 
| 119 | 
            +
                        enqueue_delayed_items_for_timestamp(timestamp)
         | 
| 120 | 
            +
                        timestamp = Resque.next_delayed_timestamp(at_time)
         | 
| 121 | 
            +
                      end
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
                  
         | 
| 125 | 
            +
                  # Enqueues all delayed jobs for a timestamp
         | 
| 126 | 
            +
                  def enqueue_delayed_items_for_timestamp(timestamp)
         | 
| 127 | 
            +
                    item = nil
         | 
| 128 | 
            +
                    begin
         | 
| 129 | 
            +
                      handle_shutdown do
         | 
| 130 | 
            +
                        if item = Resque.next_item_for_timestamp(timestamp)
         | 
| 131 | 
            +
                          log "queuing #{item['class']} [delayed]"
         | 
| 132 | 
            +
                          queue = item['queue'] || Resque.queue_from_class(constantize(item['class']))
         | 
| 133 | 
            +
                          # Support custom job classes like job with status
         | 
| 134 | 
            +
                          if (job_klass = item['custom_job_class']) && (job_klass != 'Resque::Job')
         | 
| 135 | 
            +
                            # custom job classes not supporting the same API calls must implement the #schedule method
         | 
| 136 | 
            +
                            constantize(job_klass).scheduled(queue, item['class'], *item['args'])
         | 
| 137 | 
            +
                          else
         | 
| 138 | 
            +
                            klass, args = item['class'], item['args']
         | 
| 139 | 
            +
                            Resque.enqueue(constantize(klass), *args)
         | 
| 140 | 
            +
                          end
         | 
| 141 | 
            +
                        end
         | 
| 142 | 
            +
                      end
         | 
| 143 | 
            +
                    # continue processing until there are no more ready items in this timestamp
         | 
| 144 | 
            +
                    end while !item.nil?
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                  def handle_shutdown
         | 
| 148 | 
            +
                    exit if @shutdown
         | 
| 149 | 
            +
                    yield
         | 
| 150 | 
            +
                    exit if @shutdown
         | 
| 151 | 
            +
                  end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                  # Enqueues a job based on a config hash
         | 
| 154 | 
            +
                  def enqueue_from_config(config)
         | 
| 155 | 
            +
                    args = config['args'] || config[:args]
         | 
| 156 | 
            +
                    klass_name = config['class'] || config[:class]
         | 
| 157 | 
            +
                    params = args.is_a?(Hash) ? [args] : Array(args)
         | 
| 158 | 
            +
                    queue = config['queue'] || config[:queue] || Resque.queue_from_class(constantize(klass_name))
         | 
| 159 | 
            +
                    # Support custom job classes like job with status
         | 
| 160 | 
            +
                    if (job_klass = config['custom_job_class']) && (job_klass != 'Resque::Job')
         | 
| 161 | 
            +
                      # custom job classes not supporting the same API calls must implement the #schedule method
         | 
| 162 | 
            +
                      constantize(job_klass).scheduled(queue, klass_name, *params)
         | 
| 163 | 
            +
                    else
         | 
| 164 | 
            +
                      Resque.enqueue(constantize(klass_name), *params)
         | 
| 165 | 
            +
                    end        
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  def rufus_scheduler
         | 
| 169 | 
            +
                    @rufus_scheduler ||= Rufus::Scheduler.start_new
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  # Stops old rufus scheduler and creates a new one.  Returns the new
         | 
| 173 | 
            +
                  # rufus scheduler
         | 
| 174 | 
            +
                  def clear_schedule!
         | 
| 175 | 
            +
                    rufus_scheduler.stop
         | 
| 176 | 
            +
                    @rufus_scheduler = nil
         | 
| 177 | 
            +
                    @@scheduled_jobs = {}
         | 
| 178 | 
            +
                    rufus_scheduler
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
                  
         | 
| 181 | 
            +
                  def reload_schedule!
         | 
| 182 | 
            +
                    procline "Reloading Schedule"
         | 
| 183 | 
            +
                    clear_schedule!
         | 
| 184 | 
            +
                    Resque.reload_schedule!
         | 
| 185 | 
            +
                    load_schedule!
         | 
| 186 | 
            +
                  end
         | 
| 187 | 
            +
                  
         | 
| 188 | 
            +
                  def update_schedule
         | 
| 189 | 
            +
                    if Resque.schedules_changed.count > 0
         | 
| 190 | 
            +
                      procline "Updating schedule"
         | 
| 191 | 
            +
                      Resque.reload_schedule!
         | 
| 192 | 
            +
                      Resque.pop_schedules_changed do |schedule_name|
         | 
| 193 | 
            +
                        if Resque.schedule.keys.include?(schedule_name)
         | 
| 194 | 
            +
                          unschedule_job(schedule_name)
         | 
| 195 | 
            +
                          load_schedule_job(schedule_name, Resque.schedule[schedule_name])
         | 
| 196 | 
            +
                        else
         | 
| 197 | 
            +
                          unschedule_job(schedule_name)
         | 
| 198 | 
            +
                        end
         | 
| 199 | 
            +
                      end
         | 
| 200 | 
            +
                      procline "Schedules Loaded"
         | 
| 201 | 
            +
                    end
         | 
| 202 | 
            +
                  end
         | 
| 203 | 
            +
                  
         | 
| 204 | 
            +
                  def unschedule_job(name)
         | 
| 205 | 
            +
                    if scheduled_jobs[name]
         | 
| 206 | 
            +
                      log "Removing schedule #{name}"
         | 
| 207 | 
            +
                      scheduled_jobs[name].unschedule
         | 
| 208 | 
            +
                      @@scheduled_jobs.delete(name)
         | 
| 209 | 
            +
                    end
         | 
| 210 | 
            +
                  end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                  # Sleeps and returns true
         | 
| 213 | 
            +
                  def poll_sleep
         | 
| 214 | 
            +
                    @sleeping = true
         | 
| 215 | 
            +
                    handle_shutdown { sleep 5 }
         | 
| 216 | 
            +
                    @sleeping = false
         | 
| 217 | 
            +
                    true
         | 
| 218 | 
            +
                  end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  # Sets the shutdown flag, exits if sleeping
         | 
| 221 | 
            +
                  def shutdown
         | 
| 222 | 
            +
                    @shutdown = true
         | 
| 223 | 
            +
                    exit if @sleeping
         | 
| 224 | 
            +
                  end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                  def log!(msg)
         | 
| 227 | 
            +
                    puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} #{msg}" unless mute
         | 
| 228 | 
            +
                  end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                  def log(msg)
         | 
| 231 | 
            +
                    # add "verbose" logic later
         | 
| 232 | 
            +
                    log!(msg) if verbose
         | 
| 233 | 
            +
                  end
         | 
| 234 | 
            +
                  
         | 
| 235 | 
            +
                  def procline(string)
         | 
| 236 | 
            +
                    $0 = "resque-mongo-scheduler-#{ResqueScheduler::Version}: #{string}"
         | 
| 237 | 
            +
                    log! $0
         | 
| 238 | 
            +
                  end
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
              end
         | 
| 243 | 
            +
             | 
| 244 | 
            +
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            module ResqueScheduler
         | 
| 2 | 
            +
              
         | 
| 3 | 
            +
              def search_delayed_count
         | 
| 4 | 
            +
                @@search_results.count
         | 
| 5 | 
            +
              end
         | 
| 6 | 
            +
              
         | 
| 7 | 
            +
              def search_delayed(query, start = 0, count = 1)
         | 
| 8 | 
            +
                if query.nil? || query.empty?
         | 
| 9 | 
            +
                  @@search_results = []
         | 
| 10 | 
            +
                  return []
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              
         | 
| 13 | 
            +
                start, count = [start, count].map { |n| Integer(n) }
         | 
| 14 | 
            +
                set_results = Set.new
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # For each search term, retrieve the failed jobs that contain at least one relevant field matching the regexp defined by that search term
         | 
| 17 | 
            +
                query.split.each do |term|
         | 
| 18 | 
            +
                  
         | 
| 19 | 
            +
                  partial_results = []
         | 
| 20 | 
            +
                  self.delayed_queue.find().each do |row|
         | 
| 21 | 
            +
                    row['items'].each do |job|
         | 
| 22 | 
            +
                      if job['class'] =~ /#{term}/i || job['queue'] =~ /#{term}/i
         | 
| 23 | 
            +
                        partial_results << row['_id']
         | 
| 24 | 
            +
                      else
         | 
| 25 | 
            +
                        job['args'].each do |arg|
         | 
| 26 | 
            +
                          arg.each do |key, value|
         | 
| 27 | 
            +
                            if key =~ /#{term}/i || value =~ /#{term}/i
         | 
| 28 | 
            +
                              partial_results << row['_id']
         | 
| 29 | 
            +
                            end
         | 
| 30 | 
            +
                          end
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  # If the set was empty, merge the first results, else intersect it with the current results
         | 
| 37 | 
            +
                  if set_results.empty?
         | 
| 38 | 
            +
                    set_results.merge(partial_results)
         | 
| 39 | 
            +
                  else
         | 
| 40 | 
            +
                    set_results = set_results & partial_results
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
                  
         | 
| 44 | 
            +
                # search_res will be an array containing 'count' values, starting with 'start', sorted in descending order
         | 
| 45 | 
            +
                @@search_results = set_results.to_a || []
         | 
| 46 | 
            +
                search_results = set_results.to_a[start, count]
         | 
| 47 | 
            +
                search_results || []
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            <%start = params[:start].to_i %>
         | 
| 2 | 
            +
            <%count = params[:count] ? params[:count].to_i : 50 %>
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            <%if params[:q].nil?%>
         | 
| 5 | 
            +
            <% delayed = [resque.delayed_queue_peek(start, start + 20)].flatten %>
         | 
| 6 | 
            +
            <% size = resque.delayed_queue_schedule_size %>
         | 
| 7 | 
            +
            <h1>Delayed Jobs</h1>
         | 
| 8 | 
            +
            <%else%>
         | 
| 9 | 
            +
            <% delayed = [resque.search_delayed(params[:q], start, count)].flatten %>
         | 
| 10 | 
            +
            <% size = resque.search_delayed_count %>
         | 
| 11 | 
            +
            <h1>Delayed jobs search results</h1>
         | 
| 12 | 
            +
            <%end%>
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            <p class='intro'>
         | 
| 15 | 
            +
              This list below contains the timestamps for scheduled delayed jobs.
         | 
| 16 | 
            +
            </p>
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            <% unless size.zero? %>
         | 
| 19 | 
            +
            <form method="GET" action="<%=u 'delayed'%>">
         | 
| 20 | 
            +
              <input type='text' name='q'>
         | 
| 21 | 
            +
              <input type='submit' name='' value='Search' />
         | 
| 22 | 
            +
            </form>
         | 
| 23 | 
            +
            <% end %>
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            <p class='sub'>
         | 
| 26 | 
            +
              Showing <%= start %> to <%= start + delayed.size %> of <b><%= size %></b> timestamps
         | 
| 27 | 
            +
            </p>
         | 
| 28 | 
            +
             | 
| 29 | 
            +
             | 
| 30 | 
            +
            <table>
         | 
| 31 | 
            +
              <tr>
         | 
| 32 | 
            +
                <th></th>
         | 
| 33 | 
            +
                <th>Timestamp</th>
         | 
| 34 | 
            +
                <th>Job count</th>
         | 
| 35 | 
            +
                <th>Class</th>
         | 
| 36 | 
            +
                <th>Args</th>
         | 
| 37 | 
            +
            		<th>Queue</th>
         | 
| 38 | 
            +
              </tr>
         | 
| 39 | 
            +
            	  <% delayed.each do |timestamp| %>
         | 
| 40 | 
            +
            	    <tr>
         | 
| 41 | 
            +
            	      <td>
         | 
| 42 | 
            +
            	        <form action="<%= url "/delayed/queue_now" %>" method="post" style="margin-top: 0px;">
         | 
| 43 | 
            +
            	          <input type="hidden" name="timestamp" value="<%= timestamp.to_i %>">
         | 
| 44 | 
            +
            	          <input type="submit" value="Queue now">
         | 
| 45 | 
            +
            	        </form>
         | 
| 46 | 
            +
            	      </td>
         | 
| 47 | 
            +
            	      <td><a href="<%= url "delayed/#{timestamp}" %>"><%= format_time(Time.at(timestamp)) %></a></td>
         | 
| 48 | 
            +
            	      <td><%= delayed_timestamp_size = resque.delayed_timestamp_size(timestamp) %></td>
         | 
| 49 | 
            +
            	      <% job = resque.delayed_timestamp_peek(timestamp, 0, 1).first %>
         | 
| 50 | 
            +
            	      <td>
         | 
| 51 | 
            +
            	        <% if job && delayed_timestamp_size == 1 %>
         | 
| 52 | 
            +
            	          <%= h(job['class']) %>
         | 
| 53 | 
            +
            	        <% else %>
         | 
| 54 | 
            +
            	          <a href="<%= url "delayed/#{timestamp}" %>">see details</a>
         | 
| 55 | 
            +
            	        <% end %>
         | 
| 56 | 
            +
            	      </td>
         | 
| 57 | 
            +
            	      <td><%= h(job['args'].inspect) if job && delayed_timestamp_size == 1 %></td>
         | 
| 58 | 
            +
            				<td><%= h(job['queue'].inspect) if job && delayed_timestamp_size == 1 %></td>
         | 
| 59 | 
            +
            	    </tr>
         | 
| 60 | 
            +
            	  <% end %>
         | 
| 61 | 
            +
            </table>
         | 
| 62 | 
            +
             | 
| 63 | 
            +
             | 
| 64 | 
            +
            <%= partial :next_more, :start => start, :count => count, :size => size %>
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            <% timestamp = params[:timestamp].to_i %>
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            <h1>Delayed jobs scheduled for <%= format_time(Time.at(timestamp)) %></h1>
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            <p class='sub'>Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = resque.delayed_timestamp_size(timestamp)%></b> jobs</p>
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            <table class='jobs'>
         | 
| 8 | 
            +
              <tr>
         | 
| 9 | 
            +
                <th>Class</th>
         | 
| 10 | 
            +
                <th>Args</th>
         | 
| 11 | 
            +
              </tr>
         | 
| 12 | 
            +
              <% jobs = resque.delayed_timestamp_peek(timestamp, start, 20) %>
         | 
| 13 | 
            +
              <% jobs.each do |job| %>
         | 
| 14 | 
            +
                <tr>
         | 
| 15 | 
            +
                  <td class='class'><%= job['class'] %></td>
         | 
| 16 | 
            +
                  <td class='args'><%=h job['args'].inspect %></td>
         | 
| 17 | 
            +
                </tr>
         | 
| 18 | 
            +
              <% end %>
         | 
| 19 | 
            +
              <% if jobs.empty? %>
         | 
| 20 | 
            +
                <tr>
         | 
| 21 | 
            +
                  <td class='no-data' colspan='2'>There are no pending jobs scheduled for this time.</td>
         | 
| 22 | 
            +
                </tr>
         | 
| 23 | 
            +
              <% end %>
         | 
| 24 | 
            +
            </table>
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            <%= partial :next_more, :start => start, :size => size %>
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            <h1>Schedule</h1>
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            <p class='intro'>
         | 
| 4 | 
            +
              The list below contains all scheduled jobs.  Click "Queue now" to queue
         | 
| 5 | 
            +
              a job immediately.
         | 
| 6 | 
            +
            </p>
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            <table>
         | 
| 9 | 
            +
              <tr>
         | 
| 10 | 
            +
                <th></th>
         | 
| 11 | 
            +
                <th>Name</th>
         | 
| 12 | 
            +
                <th>Description</th>
         | 
| 13 | 
            +
                <th>Interval</th>
         | 
| 14 | 
            +
                <th>Class</th>
         | 
| 15 | 
            +
                <th>Queue</th>
         | 
| 16 | 
            +
                <th>Arguments</th>
         | 
| 17 | 
            +
              </tr>
         | 
| 18 | 
            +
              <% Resque.schedule.keys.sort.each do |name| %>
         | 
| 19 | 
            +
                <% config = Resque.schedule[name] %>
         | 
| 20 | 
            +
                <tr>
         | 
| 21 | 
            +
                  <td>
         | 
| 22 | 
            +
                    <form action="<%= url "/schedule/requeue" %>" method="post">
         | 
| 23 | 
            +
                      <input type="hidden" name="job_name" value="<%= h name %>">
         | 
| 24 | 
            +
                      <input type="submit" value="Queue now">
         | 
| 25 | 
            +
                    </form>
         | 
| 26 | 
            +
                  </td>
         | 
| 27 | 
            +
                  <td><%= h name %></td>
         | 
| 28 | 
            +
                  <td><%= h config['description'] %></td>
         | 
| 29 | 
            +
                  <td style="white-space:nowrap"><%= (config['cron'].nil? && !config['every'].nil?) ?
         | 
| 30 | 
            +
                                                     h('every: ' + config['every']) : 
         | 
| 31 | 
            +
                                                     h('cron: ' + config['cron']) %></td>
         | 
| 32 | 
            +
                  <td><%= (config['class'].nil? && !config['custom_job_class'].nil?) ?
         | 
| 33 | 
            +
                          h(config['custom_job_class']) :
         | 
| 34 | 
            +
                          h(config['class']) %></td>
         | 
| 35 | 
            +
                  <td><%= h config['queue'] || queue_from_class_name(config['class']) %></td>
         | 
| 36 | 
            +
                  <td><%= h config['args'].inspect %></td>
         | 
| 37 | 
            +
                </tr>
         | 
| 38 | 
            +
              <% end %>
         | 
| 39 | 
            +
            </table>
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            # Extend Resque::Server to add tabs
         | 
| 3 | 
            +
            module ResqueScheduler
         | 
| 4 | 
            +
              
         | 
| 5 | 
            +
              module Server
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def self.included(base)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  base.class_eval do
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    helpers do
         | 
| 12 | 
            +
                      def format_time(t)
         | 
| 13 | 
            +
                        t.strftime("%Y-%m-%d %H:%M:%S")
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      def queue_from_class_name(class_name)
         | 
| 17 | 
            +
                        Resque.queue_from_class(Resque.constantize(class_name))
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    get "/schedule" do
         | 
| 22 | 
            +
                      Resque.reload_schedule! if Resque::Scheduler.dynamic
         | 
| 23 | 
            +
                      # Is there a better way to specify alternate template locations with sinatra?
         | 
| 24 | 
            +
                      erb File.read(File.join(File.dirname(__FILE__), 'server/views/scheduler.erb'))
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    post "/schedule/requeue" do
         | 
| 28 | 
            +
                      config = Resque.schedule[params['job_name']]
         | 
| 29 | 
            +
                      Resque::Scheduler.enqueue_from_config(config)
         | 
| 30 | 
            +
                      redirect url("/overview")
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                    
         | 
| 33 | 
            +
                    get "/delayed" do
         | 
| 34 | 
            +
                      # Is there a better way to specify alternate template locations with sinatra?
         | 
| 35 | 
            +
                      erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed.erb'))
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    get "/delayed/:timestamp" do
         | 
| 39 | 
            +
                      # Is there a better way to specify alternate template locations with sinatra?
         | 
| 40 | 
            +
                      erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed_timestamp.erb'))
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                    
         | 
| 43 | 
            +
                    post "/delayed/queue_now" do
         | 
| 44 | 
            +
                      timestamp = params['timestamp']
         | 
| 45 | 
            +
                      Resque::Scheduler.enqueue_delayed_items_for_timestamp(timestamp.to_i) if timestamp.to_i > 0
         | 
| 46 | 
            +
                      redirect url("/overview")
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                Resque::Server.tabs << 'Schedule'
         | 
| 54 | 
            +
                Resque::Server.tabs << 'Delayed'
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
              
         | 
| 58 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # require 'resque/tasks'
         | 
| 2 | 
            +
            # will give you the resque tasks
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            namespace :resque do
         | 
| 5 | 
            +
              task :setup
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              desc "Start Resque Scheduler"
         | 
| 8 | 
            +
              task :scheduler => :scheduler_setup do
         | 
| 9 | 
            +
                gem 'resque-mongo'
         | 
| 10 | 
            +
                require 'resque'
         | 
| 11 | 
            +
                require 'resque_scheduler'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                Resque::Scheduler.verbose = true if ENV['VERBOSE']
         | 
| 14 | 
            +
                Resque::Scheduler.run
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              task :scheduler_setup do
         | 
| 18 | 
            +
                if ENV['INITIALIZER_PATH']
         | 
| 19 | 
            +
                  load ENV['INITIALIZER_PATH'].to_s.strip
         | 
| 20 | 
            +
                else
         | 
| 21 | 
            +
                  Rake::Task['resque:setup'].invoke
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            end
         |