messed 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/README.rdoc +22 -0
- data/Rakefile +76 -0
- data/VERSION +1 -0
- data/application_spec/Rakefile +12 -0
- data/application_spec/app/runner.rb +3 -0
- data/application_spec/bin/runner +18 -0
- data/application_spec/bin/status +36 -0
- data/application_spec/bin/web +29 -0
- data/application_spec/config/environment.rb +8 -0
- data/application_spec/config/environments/development.rb +0 -0
- data/application_spec/log/development.log +6 -0
- data/application_spec/spec/application_spec.rb +12 -0
- data/application_spec/spec/spec.opts +7 -0
- data/application_spec/spec/spec_helper.rb +42 -0
- data/bin/messed +9 -0
- data/lib/messed/booter.rb +114 -0
- data/lib/messed/configuration.rb +94 -0
- data/lib/messed/controller/helper.rb +8 -0
- data/lib/messed/controller/processing.rb +22 -0
- data/lib/messed/controller/respond.rb +48 -0
- data/lib/messed/controller.rb +17 -0
- data/lib/messed/em_runner.rb +50 -0
- data/lib/messed/interface/adapter/sms.rb +4 -0
- data/lib/messed/interface/adapter/twitter_consumer.rb +35 -0
- data/lib/messed/interface/adapter/twitter_search.rb +111 -0
- data/lib/messed/interface/adapter/twitter_sender.rb +44 -0
- data/lib/messed/interface/adapter/twitter_streaming.rb +50 -0
- data/lib/messed/interface/adapter.rb +68 -0
- data/lib/messed/interface/runner.rb +20 -0
- data/lib/messed/interface.rb +74 -0
- data/lib/messed/logger.rb +50 -0
- data/lib/messed/matcher.rb +50 -0
- data/lib/messed/message/twitter.rb +23 -0
- data/lib/messed/message.rb +35 -0
- data/lib/messed/queue/beanstalk.rb +56 -0
- data/lib/messed/queue.rb +17 -0
- data/lib/messed/session/memcache.rb +40 -0
- data/lib/messed/session.rb +7 -0
- data/lib/messed/tasks/console.rb +70 -0
- data/lib/messed/tasks/generation.rb +12 -0
- data/lib/messed/tasks/runner.rb +50 -0
- data/lib/messed/tasks/status.rb +37 -0
- data/lib/messed/tasks.rb +9 -0
- data/lib/messed/util/remote_status.rb +45 -0
- data/lib/messed.rb +235 -0
- data/patterns/messed/Pattern +24 -0
- data/patterns/messed/Rakefile +12 -0
- data/patterns/messed/app/runner.rb +5 -0
- data/patterns/messed/bin/console +3 -0
- data/patterns/messed/bin/runner +3 -0
- data/patterns/messed/bin/status +3 -0
- data/patterns/messed/bin/web +13 -0
- data/patterns/messed/config/environment.rb +15 -0
- data/patterns/messed/config/environments/development.rb +2 -0
- data/patterns/messed/config/environments/production.rb +0 -0
- data/patterns/messed/config/environments/test.rb +0 -0
- data/patterns/messed/spec/application_spec.rb +12 -0
- data/patterns/messed/spec/spec.opts +7 -0
- data/patterns/messed/spec/spec_helper.rb +44 -0
- data/spec/adapter/applications/twitter_search/app/application.rb +3 -0
- data/spec/adapter/applications/twitter_search/app/runner.rb +7 -0
- data/spec/adapter/applications/twitter_search/bin/incoming +89 -0
- data/spec/adapter/applications/twitter_search/bin/runner +21 -0
- data/spec/adapter/applications/twitter_search/bin/status +36 -0
- data/spec/adapter/applications/twitter_search/config/environment.rb +11 -0
- data/spec/adapter/applications/twitter_search/config/environments/development.rb +0 -0
- data/spec/adapter/applications/twitter_search/log/development.log +1006 -0
- data/spec/adapter/applications/twitter_sender/app/application.rb +0 -0
- data/spec/adapter/applications/twitter_sender/app/runner.rb +0 -0
- data/spec/adapter/applications/twitter_sender/bin/incoming +89 -0
- data/spec/adapter/applications/twitter_sender/bin/runner +21 -0
- data/spec/adapter/applications/twitter_sender/bin/status +36 -0
- data/spec/adapter/applications/twitter_sender/config/environment.rb +11 -0
- data/spec/adapter/applications/twitter_sender/config/environments/development.rb +0 -0
- data/spec/adapter/applications/twitter_sender/log/development.log +146 -0
- data/spec/adapter/http/direct_message +0 -0
- data/spec/adapter/http/twitter_search +20 -0
- data/spec/adapter/http/update +0 -0
- data/spec/adapter/twitter_search_spec.rb +36 -0
- data/spec/adapter/twitter_sender_spec.rb +64 -0
- data/spec/booter_spec.rb +7 -0
- data/spec/fixtures/booter/app/application.rb +3 -0
- data/spec/fixtures/booter/app/runner.rb +7 -0
- data/spec/fixtures/booter/bin/incoming +89 -0
- data/spec/fixtures/booter/bin/runner +21 -0
- data/spec/fixtures/booter/bin/status +36 -0
- data/spec/fixtures/booter/config/environment.rb +15 -0
- data/spec/fixtures/booter/config/environments/development.rb +0 -0
- data/spec/fixtures/booter/log/development.log +11 -0
- data/spec/message_spec.rb +52 -0
- data/spec/session_spec.rb +34 -0
- data/spec/spec.opts +7 -0
- data/spec/spec_helper.rb +8 -0
- data/test/app/runner.rb +5 -0
- data/test/config/environment.rb +15 -0
- data/test/config/environments/development.rb +2 -0
- data/test/config/environments/production.rb +0 -0
- data/test/config/environments/test.rb +0 -0
- data/test/spec/application_spec.rb +12 -0
- data/test/spec/spec_helper.rb +44 -0
- metadata +291 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
class Messed
|
2
|
+
class Interface
|
3
|
+
class Adapter
|
4
|
+
class TwitterConsumer < Adapter
|
5
|
+
|
6
|
+
attr_accessor :started_at, :packets_processed, :errors, :last_error, :last_ok, :last_status
|
7
|
+
|
8
|
+
def init
|
9
|
+
@started_at = Time.new
|
10
|
+
@packets_processed = 0
|
11
|
+
@errors = 0
|
12
|
+
@last_error = nil
|
13
|
+
@last_ok = nil
|
14
|
+
@last_status = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def type
|
18
|
+
:twitter
|
19
|
+
end
|
20
|
+
|
21
|
+
def status
|
22
|
+
{
|
23
|
+
:started_at => started_at,
|
24
|
+
:packets_processed => packets_processed,
|
25
|
+
:errors => errors,
|
26
|
+
:last_error => last_error,
|
27
|
+
:last_ok => last_ok,
|
28
|
+
:last_status => last_status
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
class Messed
|
5
|
+
class Interface
|
6
|
+
class Adapter
|
7
|
+
class TwitterSearch < TwitterConsumer
|
8
|
+
|
9
|
+
attr_reader :packets_processed
|
10
|
+
|
11
|
+
def build_query
|
12
|
+
Rack::Utils.build_query(interface.configuration.options[:fetch][:query])
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
@ids ||= default_ids
|
17
|
+
# do work.
|
18
|
+
begin
|
19
|
+
query = build_query
|
20
|
+
logger.debug "Twitter Search -> #{query.inspect}"
|
21
|
+
http = EventMachine::HttpRequest.new("http://#{interface.configuration.options[:fetch][:host]}/#{interface.configuration.options[:fetch][:path]}").
|
22
|
+
get(:query => query, :timeout => 30)
|
23
|
+
http.callback {
|
24
|
+
self.last_status = http.response_header.status
|
25
|
+
case http.response_header.status
|
26
|
+
when 200
|
27
|
+
self.last_ok = Time.new
|
28
|
+
data = JSON.parse(http.response)
|
29
|
+
interface.configuration.options[:fetch][:query][:since_id] = data['max_id'] if interface.configuration.options[:fetch][:query]
|
30
|
+
@test_set = Set.new(@ids)
|
31
|
+
data['results'].each do |result|
|
32
|
+
result_to_message(result)
|
33
|
+
end
|
34
|
+
trim_ids
|
35
|
+
end
|
36
|
+
EM.add_timer(interface.configuration.options[:interval]) do
|
37
|
+
start
|
38
|
+
end
|
39
|
+
}
|
40
|
+
http.errback {
|
41
|
+
self.errors += 1
|
42
|
+
self.last_error = Time.new
|
43
|
+
EM.add_timer(interface.configuration.options[:interval]) do
|
44
|
+
start
|
45
|
+
end
|
46
|
+
}
|
47
|
+
rescue RuntimeError
|
48
|
+
EM.add_timer(interface.configuration.options[:interval]) do
|
49
|
+
start
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def id_retention_count
|
55
|
+
interface.configuration.options[:id_retention_size] || 500
|
56
|
+
end
|
57
|
+
|
58
|
+
def trim_ids
|
59
|
+
while @ids.size > id_retention_count
|
60
|
+
@ids.shift
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def store_ids?
|
65
|
+
id_retention_file
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_ids
|
69
|
+
File.open(id_retention_file, 'w') { |f| f << @ids.to_json }
|
70
|
+
end
|
71
|
+
|
72
|
+
def read_ids
|
73
|
+
@ids = JSON.parse(File.read(id_retention_file))
|
74
|
+
end
|
75
|
+
|
76
|
+
def default_ids
|
77
|
+
store_ids? ? read_ids : []
|
78
|
+
rescue
|
79
|
+
logger.error "Twitter Search: Unable to load ids from #{id_retention_file.inspect}"
|
80
|
+
[]
|
81
|
+
end
|
82
|
+
|
83
|
+
def id_retention_file
|
84
|
+
interface.configuration.options[:id_retention_tmp_file]
|
85
|
+
end
|
86
|
+
|
87
|
+
def result_to_message(result)
|
88
|
+
unless @test_set.include?(result['id'])
|
89
|
+
message = Message::Twitter.new do |m|
|
90
|
+
m.body = result['text']
|
91
|
+
m.from = result['from_user']
|
92
|
+
m.to = result['to_user_id']
|
93
|
+
m.created_at = Time.rfc2822(result['created_at'])
|
94
|
+
m.profile_image_url = result['profile_image_url']
|
95
|
+
m.id = result['id']
|
96
|
+
m.geo = result['geo']
|
97
|
+
m.from_user_id = result['from_user_id']
|
98
|
+
m.iso_language_code = result['iso_language_code']
|
99
|
+
m.source = result['source']
|
100
|
+
end
|
101
|
+
@ids << message.id
|
102
|
+
@packets_processed += 1
|
103
|
+
interface.application.incoming << message
|
104
|
+
logger.debug "Twitter Search: Adding message #{message.id}: #{message.body} to incoming queue"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'twitter'
|
2
|
+
|
3
|
+
class Messed
|
4
|
+
class Interface
|
5
|
+
class Adapter
|
6
|
+
class TwitterSender < Adapter
|
7
|
+
|
8
|
+
def message_class
|
9
|
+
Messed::Message::Twitter
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
jack = EM::Beanstalk.new
|
14
|
+
jack.watch(interface.application.outgoing.tube) do
|
15
|
+
jack.use(interface.application.outgoing.tube) do
|
16
|
+
jack.each_job do |job|
|
17
|
+
process_outgoing(job, message_class.from_json(job.body))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def process_outgoing(job, message)
|
24
|
+
if message.private?
|
25
|
+
req = EventMachine::HttpRequest.new("http://twitter.com/direct_messages/new.json")
|
26
|
+
data = {:body => {:user => message.to_user_id, :text => message.body}, :timeout => 30, :head => {'authorization' => [interface.configuration.options[:username], interface.configuration.options[:password]]}}
|
27
|
+
data[:body][:in_reply_to] = message.in_reply_to if message.in_reply_to
|
28
|
+
http = req.post(data)
|
29
|
+
http.callback {
|
30
|
+
job.delete
|
31
|
+
}
|
32
|
+
else
|
33
|
+
http = EventMachine::HttpRequest.new("http://twitter.com/statuses/update.json").
|
34
|
+
post(:body => {:status => message.body}, :timeout => 30)
|
35
|
+
http.callback {
|
36
|
+
job.delete
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'twitter/json_stream'
|
2
|
+
|
3
|
+
class Messed
|
4
|
+
class Interface
|
5
|
+
class Adapter
|
6
|
+
class TwitterStreaming < TwitterConsumer
|
7
|
+
|
8
|
+
include Logger::LoggingModule
|
9
|
+
|
10
|
+
def start
|
11
|
+
stream = Twitter::JSONStream.connect(
|
12
|
+
:path => "/1/statuses/#{interface.configuration.options['streaming']['type']}.json?track=%23classicmoviequotes",
|
13
|
+
:auth => "#{interface.configuration.options['username']}:#{interface.configuration.options['password']}"
|
14
|
+
)
|
15
|
+
|
16
|
+
stream.each_item do |item|
|
17
|
+
self.last_ok = Time.new
|
18
|
+
result = JSON.parse(item)
|
19
|
+
message = Message::Twitter.new do |m|
|
20
|
+
m.body = result['text']
|
21
|
+
m.from = result['from_user']
|
22
|
+
m.to = result['to_user_id']
|
23
|
+
m.created_at = Time.rfc2822(result['created_at'])
|
24
|
+
m.profile_image_url = result['profile_image_url']
|
25
|
+
m.id = result['id']
|
26
|
+
m.geo = result['geo']
|
27
|
+
m.from_user_id = result['from_user_id']
|
28
|
+
m.iso_language_code = result['iso_language_code']
|
29
|
+
m.source = result['source']
|
30
|
+
end
|
31
|
+
self.packets_processed += 1
|
32
|
+
interface.application.incoming << message
|
33
|
+
end
|
34
|
+
|
35
|
+
stream.on_error do |message|
|
36
|
+
logger.error "message #{message.inspect}"
|
37
|
+
self.errors += 1
|
38
|
+
self.last_error = message
|
39
|
+
end
|
40
|
+
|
41
|
+
stream.on_max_reconnects do |timeout, retries|
|
42
|
+
logger.error "message -> #{timeout} #{retries}"
|
43
|
+
# Something is wrong on your side. Send yourself an email.
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Messed
|
4
|
+
class Interface
|
5
|
+
class Adapter
|
6
|
+
|
7
|
+
include Logger::LoggingModule
|
8
|
+
|
9
|
+
autoload :TwitterSearch, File.join('messed', 'interface', 'adapter', 'twitter_search')
|
10
|
+
autoload :TwitterSender, File.join('messed', 'interface', 'adapter', 'twitter_sender')
|
11
|
+
autoload :TwitterStreaming, File.join('messed', 'interface', 'adapter', 'twitter_streaming')
|
12
|
+
|
13
|
+
# abstract class for twitter consumption
|
14
|
+
autoload :TwitterConsumer, File.join(File.dirname(__FILE__), 'adapter', 'twitter_consumer')
|
15
|
+
|
16
|
+
Registry = {}
|
17
|
+
def self.register_for_name(name, class_name)
|
18
|
+
Registry[name] = class_name
|
19
|
+
end
|
20
|
+
|
21
|
+
register_for_name :twitter_search, 'Messed::Interface::Adapter::TwitterSearch'
|
22
|
+
register_for_name :twitter_sender, 'Messed::Interface::Adapter::TwitterSender'
|
23
|
+
register_for_name :twitter_streaming, 'Messed::Interface::Adapter::TwitterStreaming'
|
24
|
+
|
25
|
+
def self.for_name(name)
|
26
|
+
class_name = Registry[name]
|
27
|
+
class_name ?
|
28
|
+
class_name.constantize :
|
29
|
+
raise("No adapter for #{name}")
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :interface
|
33
|
+
|
34
|
+
def initialize(interface)
|
35
|
+
@interface = interface
|
36
|
+
init
|
37
|
+
end
|
38
|
+
|
39
|
+
def save_state(state)
|
40
|
+
File.open(state_file, 'w') {|f| f << state.to_json }
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_state
|
44
|
+
JSON.parse(File.read(state_file))
|
45
|
+
end
|
46
|
+
|
47
|
+
def state_file
|
48
|
+
"/tmp/#{name}.state"
|
49
|
+
end
|
50
|
+
|
51
|
+
# any post initialization goes here
|
52
|
+
def init
|
53
|
+
end
|
54
|
+
|
55
|
+
def type
|
56
|
+
raise "method type must be defined"
|
57
|
+
end
|
58
|
+
|
59
|
+
def start(detach)
|
60
|
+
raise "method start must be defined"
|
61
|
+
end
|
62
|
+
|
63
|
+
def send(message)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'thin'
|
2
|
+
|
3
|
+
class Messed
|
4
|
+
class Interface
|
5
|
+
class Runner
|
6
|
+
|
7
|
+
attr_reader :runnable, :options
|
8
|
+
|
9
|
+
def initialize(runnable, options)
|
10
|
+
@runnable = runnable
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
runnable.start(options[:detach])
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class Messed
|
2
|
+
class Interface
|
3
|
+
|
4
|
+
include Logger::LoggingModule
|
5
|
+
|
6
|
+
autoload :Runner, File.join('messed', 'interface', 'runner')
|
7
|
+
autoload :Adapter, File.join('messed', 'interface', 'adapter')
|
8
|
+
|
9
|
+
def self.interface_from_configuration(booter, name, configuration)
|
10
|
+
interface = Interface.new
|
11
|
+
interface.send(:name=, name)
|
12
|
+
interface.send(:configuration=, configuration)
|
13
|
+
interface.send(:adapter=, Adapter.for_name(configuration.adapter).new(interface))
|
14
|
+
interface.send(:booter=, booter)
|
15
|
+
interface
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"#{Array(configuration['mode']) * ', '} ->> #{name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def status
|
23
|
+
{
|
24
|
+
'adapter' => adapter.status,
|
25
|
+
'configuration' => configuration,
|
26
|
+
'name' => name
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :booter, :configuration, :adapter, :name, :started_at
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@started_at = Time.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def application
|
37
|
+
booter.application
|
38
|
+
end
|
39
|
+
|
40
|
+
def status_host
|
41
|
+
configuration.status_address || '0.0.0.0'
|
42
|
+
end
|
43
|
+
|
44
|
+
def status_port
|
45
|
+
configuration.status_port || 11190
|
46
|
+
end
|
47
|
+
|
48
|
+
def stop
|
49
|
+
Process.kill("INT", booter.read_pid_file(configuration.pid_file))
|
50
|
+
exit(0)
|
51
|
+
end
|
52
|
+
|
53
|
+
def start
|
54
|
+
begin
|
55
|
+
EM.start_server(status_host, status_port, EMRunner::StatusHandler) do |c|
|
56
|
+
c.interface = self
|
57
|
+
end
|
58
|
+
logger.info "Status handler for #{self.class} started on #{status_host}:#{status_port}"
|
59
|
+
rescue RuntimeError => e
|
60
|
+
if e.message =~ /no acceptor/
|
61
|
+
logger.error "Unable to start status handler"
|
62
|
+
else
|
63
|
+
raise e
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
booter.write_pid_file(configuration.pid_file)
|
68
|
+
adapter.start
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
attr_writer :booter, :configuration, :adapter, :name
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class Messed
|
4
|
+
class Logger
|
5
|
+
module LoggingModule
|
6
|
+
def self.included(cls)
|
7
|
+
cls.class_eval(<<-HERE_DOC, __FILE__, __LINE__)
|
8
|
+
|
9
|
+
def self.logger
|
10
|
+
Messed::Logger.instance.logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logger=(logger)
|
14
|
+
Messed::Logger.instance.logger = logger
|
15
|
+
end
|
16
|
+
|
17
|
+
HERE_DOC
|
18
|
+
end
|
19
|
+
|
20
|
+
def logger
|
21
|
+
Messed::Logger.instance.logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger=(logger)
|
25
|
+
Messed::Logger.instance.logger = logger
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :logger
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
setup_logger
|
34
|
+
end
|
35
|
+
private :initialize
|
36
|
+
|
37
|
+
def self.instance
|
38
|
+
@@instance
|
39
|
+
end
|
40
|
+
|
41
|
+
def setup_logger(logger = ::Logger.new(STDOUT), log_level = :debug)
|
42
|
+
@logger = logger
|
43
|
+
@logger.level = ::Logger.const_get(log_level.to_s.upcase.to_sym) if log_level
|
44
|
+
end
|
45
|
+
|
46
|
+
@@instance = self.new
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Messed
|
2
|
+
class Matcher
|
3
|
+
|
4
|
+
attr_accessor :destination
|
5
|
+
|
6
|
+
def stop_processing?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
class Conditional < Matcher
|
11
|
+
|
12
|
+
attr_reader :matches
|
13
|
+
|
14
|
+
def initialize(body_matcher, other_matchers = nil)
|
15
|
+
@body_matcher = body_matcher
|
16
|
+
@other_matchers = other_matchers
|
17
|
+
end
|
18
|
+
|
19
|
+
def match?(message)
|
20
|
+
matches = true
|
21
|
+
self.matches = nil
|
22
|
+
if @body_matcher
|
23
|
+
matches &&= @body_matcher === message.body
|
24
|
+
self.matches = Regexp.last_match if matches && @body_matcher.is_a?(Regexp)
|
25
|
+
end
|
26
|
+
|
27
|
+
if @other_matchers
|
28
|
+
keys = @other_matchers.keys
|
29
|
+
unless matches
|
30
|
+
key = keys.pop
|
31
|
+
matches &&= @other_matchers[key] === message.send(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
matches
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
attr_writer :matches
|
39
|
+
end
|
40
|
+
|
41
|
+
class Always < Matcher
|
42
|
+
|
43
|
+
def match?(message)
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Messed
|
2
|
+
class Message
|
3
|
+
class Twitter < Message
|
4
|
+
|
5
|
+
attr_accessor :created_at, :profile_image_url, :id, :geo, :from_user_id, :iso_language_code, :source, :private, :to_user_id
|
6
|
+
hash_accessor :profile_image_url, :id, :geo, :from_user_id, :iso_language_code, :source, :to_user_id, :private
|
7
|
+
hash_convert :created_at => Hashify::Convert::Time
|
8
|
+
|
9
|
+
def initialize(body = nil)
|
10
|
+
super(body)
|
11
|
+
self.private = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def unique_id
|
15
|
+
from_user_id
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :private?, :private
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Messed
|
2
|
+
|
3
|
+
class Message
|
4
|
+
|
5
|
+
autoload :Twitter, File.join('messed', 'message', 'twitter')
|
6
|
+
|
7
|
+
include Hashify
|
8
|
+
include Hashify::Json
|
9
|
+
|
10
|
+
attr_accessor :body
|
11
|
+
attr_accessor :from
|
12
|
+
attr_accessor :to
|
13
|
+
attr_accessor :enqueued_at
|
14
|
+
attr_accessor :in_reply_to
|
15
|
+
|
16
|
+
hash_accessor :body, :from, :to
|
17
|
+
hash_convert :enqueued_at => Hashify::Convert::Time
|
18
|
+
hash_convert :in_reply_to => [proc{|x| x && x.to_hash}, proc{|x, parent| x && parent.class.from_hash(x)}]
|
19
|
+
|
20
|
+
def self.class_for_type(type)
|
21
|
+
case type
|
22
|
+
when :twitter
|
23
|
+
Twitter
|
24
|
+
else
|
25
|
+
raise type
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(body = nil)
|
30
|
+
self.body = body
|
31
|
+
yield self if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Messed
|
2
|
+
class Queue
|
3
|
+
class Beanstalk < Queue
|
4
|
+
|
5
|
+
include Logger::LoggingModule
|
6
|
+
|
7
|
+
attr_accessor :application
|
8
|
+
attr_reader :tube
|
9
|
+
|
10
|
+
def initialize(tube, host = '127.0.0.1', port = 11300)
|
11
|
+
@tube, @host, @port = tube, host, port
|
12
|
+
@beanstalk = ::Beanstalk::Pool.new(Array("#{host}:#{port}"))
|
13
|
+
@beanstalk.use(tube)
|
14
|
+
@beanstalk.watch(tube)
|
15
|
+
end
|
16
|
+
|
17
|
+
def status
|
18
|
+
@beanstalk.stats_tube(tube)
|
19
|
+
end
|
20
|
+
|
21
|
+
def take(block = true)
|
22
|
+
job = beanstalk.reserve
|
23
|
+
begin
|
24
|
+
message = application.message_class.from_json(job.body)
|
25
|
+
rescue JSON::ParserError
|
26
|
+
logger.error "malformed message #{job.body}"
|
27
|
+
job.delete
|
28
|
+
else
|
29
|
+
yield message
|
30
|
+
job.delete
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def <<(message)
|
35
|
+
beanstalk.put message.to_json
|
36
|
+
end
|
37
|
+
|
38
|
+
def jobs_available?
|
39
|
+
not jobs_available.zero?
|
40
|
+
end
|
41
|
+
|
42
|
+
def drain!
|
43
|
+
while jobs_available?
|
44
|
+
beanstalk.reserve.delete
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def jobs_available
|
49
|
+
beanstalk.stats_tube(tube)['current-jobs-ready']
|
50
|
+
end
|
51
|
+
protected
|
52
|
+
attr_reader :beanstalk
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/messed/queue.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'memcached'
|
2
|
+
require 'json'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
class Messed
|
6
|
+
class Session
|
7
|
+
class Memcache < Session
|
8
|
+
|
9
|
+
def initialize(config = "127.0.0.1:11211")
|
10
|
+
@connection = Memcached.new(config)
|
11
|
+
end
|
12
|
+
|
13
|
+
def reset!(id)
|
14
|
+
connection.delete(id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def with(id)
|
18
|
+
data = data(id)
|
19
|
+
begin
|
20
|
+
yield data
|
21
|
+
ensure
|
22
|
+
connection.set(key(id), data.to_json)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
attr_reader :connection
|
28
|
+
|
29
|
+
def data(id)
|
30
|
+
JSON.parse(connection.get(key(id))).strhash
|
31
|
+
rescue Memcached::NotFound
|
32
|
+
StrHash.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def key(id)
|
36
|
+
Digest::MD5.hexdigest(id.to_s)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|