afterparty 0.0.21 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ require 'active_record'
2
+ require 'afterparty/queue_helpers'
3
+ class AfterpartyJob < ::ActiveRecord::Base
4
+ # include Afterparty::QueueHelpers
5
+
6
+ validates_presence_of :job_dump, :execute_at, :queue
7
+
8
+ scope :incomplete, -> { where(completed: false).order("execute_at") }
9
+ scope :valid, -> { incomplete.where(execute_at: 10.years.ago..DateTime.now) }
10
+ scope :completed, -> { where(completed: true).order("execute_at desc") }
11
+
12
+ def self.make_with_job job, queue=:default
13
+ afterparty_job = AfterpartyJob.new
14
+ afterparty_job.job_dump = job.to_yaml
15
+ afterparty_job.execute_at = Afterparty.queue_time(job)
16
+ afterparty_job.queue = queue
17
+ afterparty_job.completed = false
18
+ afterparty_job.save
19
+ afterparty_job
20
+ end
21
+
22
+ def reify
23
+ Afterparty.load(job_dump)
24
+ end
25
+
26
+ def execute
27
+ if (j = reify)
28
+ j.run
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ require 'rails'
2
+ module Afterparty
3
+ class Engine < Rails::Engine
4
+ engine_name :afterparty
5
+
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ require 'iconv'
2
+ require 'date'
3
+
4
+ module Afterparty
5
+ class JobContainer
6
+ attr_accessor :job, :raw, :execute_at, :job_id, :queue_name
7
+
8
+ #intialized from redis's WITHSCORES function
9
+ def initialize _raw, timestamp
10
+ @execute_at = Time.at(timestamp)
11
+ begin
12
+ @job = Afterparty.load(_raw)
13
+ @job_id = job.afterparty_job_id if @job.respond_to? :afterparty_job_id
14
+ @queue_name = job.afterparty_queue if @job.respond_to? :afterparty_queue
15
+ rescue Exception => e
16
+ ap "Error during load: #{e.message}"
17
+ @job = nil
18
+ end
19
+ @raw = _raw
20
+ self
21
+ end
22
+
23
+ def job_class
24
+ if @job
25
+ @job.class
26
+ else
27
+ nil
28
+ end
29
+ end
30
+
31
+ def raw_string
32
+ ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
33
+ ic.iconv(@raw.dup + ' ')[0..-2]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,41 @@
1
+ module Afterparty
2
+ class MailerJob
3
+ attr_accessor :execute_at, :mail, :clazz, :method, :args
4
+ def initialize clazz, method, *args
5
+ # @mail = UserMailer.welcome_email(User.find(1))
6
+ @clazz = UserMailer
7
+ @method = method
8
+ @args = args
9
+ end
10
+
11
+ def run
12
+ @mail = @clazz.send @method, *@args
13
+ @mail.deliver
14
+ end
15
+
16
+ def description
17
+ desc = "Mailer: #{(@clazz || "nil")}."
18
+ desc << "Method: #{(@method || nil)}."
19
+ desc << "Args: #{(@args || nil)}"
20
+ end
21
+ end
22
+
23
+ class BasicJob
24
+ attr_accessor :object, :method, :args
25
+ def initialize object, method, *args
26
+ @object = object
27
+ @method = method
28
+ @args = args
29
+ end
30
+
31
+ def run
32
+ @object.send(:method, *@args)
33
+ end
34
+
35
+ def description
36
+ desc = "Object: #{(@object || "nil")}."
37
+ desc << "Method: #{(@method || nil)}."
38
+ desc << "Args: #{(@args || nil)}"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,53 @@
1
+ module Afterparty
2
+ class Queue
3
+ attr_accessor :options, :temp_namespace, :login_block
4
+ include Afterparty::QueueHelpers
5
+
6
+ def initialize options={}, consumer_options={}
7
+ # @consumer = ThreadedQueueConsumer.new(self, consumer_options).start
8
+ @options = options
9
+ @options[:namespace] ||= "default"
10
+ # Afterparty.add_queue @options[:namespace]
11
+ @options[:sleep] ||= 5
12
+ @mutex = Mutex.new
13
+ @options[:logger] ||= Logger.new($stderr)
14
+ end
15
+
16
+ def push job
17
+ # @mutex.synchronize do
18
+ return nil if job.nil?
19
+ queue_name = @temp_namespace || @options[:namespace]
20
+ AfterpartyJob.make_with_job job, queue_name
21
+ # end
22
+ end
23
+ alias :<< :push
24
+ alias :eng :push
25
+
26
+ def pop
27
+ # @mutex.synchronize do
28
+ while true do
29
+ unless (_job = AfterpartyJob.valid.first).nil?
30
+ ap "poppin job"
31
+ _job.completed = true
32
+ _job.save
33
+ return _job
34
+ end
35
+ sleep(@options[:sleep])
36
+ end
37
+ # end
38
+ end
39
+ end
40
+
41
+ class TestQueue < Queue
42
+ attr_accessor :completed_jobs
43
+
44
+ def initialize opts={}, consumer_opts={}
45
+ super
46
+ @completed_jobs = []
47
+ @exceptions = []
48
+ end
49
+ def handle_exception job, exception
50
+ @exceptions << [job, exception]
51
+ end
52
+ end
53
+ end
@@ -4,16 +4,18 @@ module Afterparty
4
4
  @temp_namespace = namespace
5
5
  end
6
6
 
7
- def redis_queue_name
8
- "afterparty_#{@temp_namespace || @options[:namespace]}_queue"
7
+ def redis_queue_name
8
+ puts (a = Afterparty.redis_queue_name(@temp_namespace || @options[:namespace]))
9
+ a
9
10
  end
10
11
 
11
12
  def clear
12
- redis_call :del
13
+ # redis_call :del
14
+ AfterpartyJob.destroy_all
13
15
  end
14
16
 
15
17
  def redis_call command, *args
16
- result = Afterparty.redis.send(command, redis_queue_name, *args)
18
+ result = Afterparty.redis_call (@temp_namespace || @options[:namespace]), command, *args
17
19
  @temp_namespace = nil
18
20
  result
19
21
  end
@@ -23,37 +25,110 @@ module Afterparty
23
25
  end
24
26
 
25
27
  def jobs
26
- _jobs = redis_call(:zrange, 0, -1)
27
- _jobs.each_with_index do |job, i|
28
- _jobs[i] = Marshal.load(job)
29
- end
30
- _jobs
28
+ # _jobs = redis_call(:zrange, 0, -1)
29
+ # _jobs.each_with_index do |job, i|
30
+ # _jobs[i] = Marshal.load(job)
31
+ # end
32
+ # _jobs
33
+ AfterpartyJob.incomplete
31
34
  end
32
35
 
33
36
  def jobs_with_scores
34
- redis_call :zrange, 0, -1, {withscores: true}
37
+ hash_from_scores(redis_call(:zrange, 0, -1, {withscores: true}))
35
38
  end
36
39
 
37
40
  def valid_jobs
38
- redis_call :zrangebyscore, 0, Time.now.to_i
41
+ # redis_call :zrangebyscore, 0, Time.now.to_i
42
+ AfterpartyJob.valid
43
+ end
44
+
45
+ def next_valid_job
46
+ # valid_jobs.first
47
+ AfterpartyJob.valid.first
39
48
  end
40
49
 
41
50
  def jobs_empty?
42
- count = total_jobs_count
43
- # ap count
44
- count == 0
51
+ # count = total_jobs_count
52
+ # # ap count
53
+ # count == 0
54
+ AfterpartyJob.valid.empty?
45
55
  end
46
56
 
47
57
  def total_jobs_count
48
- redis_call(:zcount, "-inf", "+inf")
58
+ # redis_call(:zcount, "-inf", "+inf")
59
+ AfterpartyJob.incomplete.count
49
60
  end
50
61
 
51
62
  def redis
52
63
  @@redis
53
64
  end
54
65
 
66
+ def last_completed
67
+ # @temp_namespace = "completed"
68
+ # redis_call(:zrange, -1, -1).first
69
+ AfterpartyJob.completed.first
70
+ end
71
+
72
+ def completed
73
+ # @temp_namespace = "completed"
74
+ # redis_call(:zrange, -20, -1).reverse
75
+ AfterpartyJob.completed
76
+ end
77
+
78
+ def completed_with_scores
79
+ @temp_namespace = "completed"
80
+ hash_from_scores(redis_call(:zrange, -20, -1, withscores: true)).reverse
81
+ end
82
+
83
+ def run(job)
84
+ real_job = job.reify
85
+ if real_job
86
+ job.execute
87
+ else
88
+ job.has_error = true
89
+ job.error_message = "Error marshaling job."
90
+ end
91
+ job.completed = true
92
+ job.completed_at = DateTime.now
93
+ job.save
94
+ rescue Exception => exception
95
+ handle_exception job, exception
96
+ end
97
+
98
+ def handle_exception(job, exception)
99
+ job.completed = true
100
+ job.completed_at = DateTime.now
101
+ job.has_error = true
102
+ job.error_message = exception.message
103
+ job.error_backtrace = exception.backtrace.join("\n")
104
+ job.save
105
+ logger_message = "Job Error: #{job.inspect}\n#{exception.message}"
106
+ logger_message << "\n#{exception.backtrace.join("\n")}"
107
+ @options[:logger].error logger_message
108
+ end
109
+
110
+ # &block takes a 'username' and 'password'
111
+ # argument. return true or false
112
+ def config_login &block
113
+ @login_block = block
114
+ end
115
+
116
+ def authenticate username, password
117
+ raise 'Must set queue.config_login to use dashboard' if @login_block.nil?
118
+ @login_block.call(username, password)
119
+ end
120
+
121
+
55
122
  private
56
123
 
124
+ def hash_from_scores raw
125
+ arr = []
126
+ raw.each do |group|
127
+ arr << Afterparty::JobContainer.new(group[0], group[1])
128
+ end
129
+ arr
130
+ end
131
+
57
132
  # returns true if job has an :execute_at value
58
133
  def job_valid? job
59
134
  job.respond_to?(:execute_at) && !job.execute_at.nil?
@@ -1,3 +1,3 @@
1
1
  module Afterparty
2
- VERSION = "0.0.21"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,46 @@
1
+ module Afterparty
2
+ class Worker
3
+ include QueueHelpers
4
+
5
+ def initialize options = {}
6
+ @options = options
7
+ @options[:adapter] ||= :redis
8
+ @options[:namespace] ||= :default
9
+ @options[:sleep] ||= 10
10
+ @options[:logger] ||= Logger.new($stderr)
11
+ self
12
+ end
13
+
14
+ def consume
15
+ @stopped = false
16
+ # puts "starting worker with namespace [#{@options[:namespace]}]."
17
+ @thread = Thread.new {
18
+ consume_sync
19
+ }
20
+ @thread
21
+ end
22
+
23
+ def consume_next
24
+ if (job = next_valid_job)
25
+ run job
26
+ end
27
+ end
28
+
29
+ def consume_sync
30
+ while !@stopped
31
+ job = next_valid_job
32
+ if job
33
+ puts "Executing job: #{job.id}"
34
+ run job
35
+ else
36
+ sleep(@options[:sleep])
37
+ end
38
+ end
39
+ end
40
+
41
+ def stop
42
+ @stopped = true
43
+ @thread.join(0) if @thread
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,9 @@
1
+ require 'rails/generators'
2
+ class AfterpartyGenerator < Rails::Generators::Base
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ def install
6
+ copy_file "jobs_migration.rb", "db/migrate/#{Time.now.strftime('%Y%m%d%H%M%S')}_create_afterparty_jobs.rb"
7
+ copy_file "initializer.rb", "config/initializers/afterparty.rb"
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ queue = Rails.configuration.queue = Afterparty::Queue.new
2
+
3
+ queue.config_login do |username, password|
4
+ # change this to something more secure!
5
+ user == "admin" && password == "password"
6
+ end
@@ -0,0 +1,21 @@
1
+ class CreateAfterpartyJobs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :afterparty_jobs do |t|
4
+ t.text :job_dump
5
+ t.string :queue
6
+ t.datetime :execute_at
7
+ t.boolean :completed
8
+ t.boolean :has_error
9
+ t.text :error_message
10
+ t.text :error_backtrace
11
+ t.datetime :completed_at
12
+
13
+ t.timestamps
14
+ end
15
+ end
16
+
17
+ def self.down
18
+ drop_table :afterparty_jobs
19
+ end
20
+
21
+ end
@@ -0,0 +1,25 @@
1
+ namespace :jobs do
2
+ require 'mail'
3
+
4
+ desc "Start a new worker"
5
+ task work: :environment do
6
+ worker = Afterparty::Worker.new
7
+ worker.consume_sync
8
+ end
9
+
10
+ # desc "Clear all jobs"
11
+ # task clear: :environment do
12
+ # Rails.configuration.queue.clear
13
+ # end
14
+
15
+ desc "List Jobs"
16
+ task list: :environment do
17
+ jobs = Rails.configuration.queue.jobs
18
+ puts "#{jobs.size} total jobs."
19
+ jobs.each do |time, job|
20
+ puts time
21
+ puts job
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ describe AfterpartyJob do
3
+ before :each do
4
+ AfterpartyJob.destroy_all
5
+ end
6
+
7
+ it "makes a job correctly" do
8
+ tester = test_job
9
+ tester.execute_at = Time.now + 10
10
+ job = AfterpartyJob.make_with_job tester
11
+ job.reload
12
+ (reloaded = job.reify).class.should == tester.class
13
+ reloaded.execute_at.should == tester.execute_at
14
+ job.execute_at.should == reloaded.execute_at
15
+ end
16
+
17
+ end