afterparty 0.0.21 → 0.1.0

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.
@@ -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