messed 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/Gemfile +8 -0
  2. data/README.rdoc +22 -0
  3. data/Rakefile +76 -0
  4. data/VERSION +1 -0
  5. data/application_spec/Rakefile +12 -0
  6. data/application_spec/app/runner.rb +3 -0
  7. data/application_spec/bin/runner +18 -0
  8. data/application_spec/bin/status +36 -0
  9. data/application_spec/bin/web +29 -0
  10. data/application_spec/config/environment.rb +8 -0
  11. data/application_spec/config/environments/development.rb +0 -0
  12. data/application_spec/log/development.log +6 -0
  13. data/application_spec/spec/application_spec.rb +12 -0
  14. data/application_spec/spec/spec.opts +7 -0
  15. data/application_spec/spec/spec_helper.rb +42 -0
  16. data/bin/messed +9 -0
  17. data/lib/messed/booter.rb +114 -0
  18. data/lib/messed/configuration.rb +94 -0
  19. data/lib/messed/controller/helper.rb +8 -0
  20. data/lib/messed/controller/processing.rb +22 -0
  21. data/lib/messed/controller/respond.rb +48 -0
  22. data/lib/messed/controller.rb +17 -0
  23. data/lib/messed/em_runner.rb +50 -0
  24. data/lib/messed/interface/adapter/sms.rb +4 -0
  25. data/lib/messed/interface/adapter/twitter_consumer.rb +35 -0
  26. data/lib/messed/interface/adapter/twitter_search.rb +111 -0
  27. data/lib/messed/interface/adapter/twitter_sender.rb +44 -0
  28. data/lib/messed/interface/adapter/twitter_streaming.rb +50 -0
  29. data/lib/messed/interface/adapter.rb +68 -0
  30. data/lib/messed/interface/runner.rb +20 -0
  31. data/lib/messed/interface.rb +74 -0
  32. data/lib/messed/logger.rb +50 -0
  33. data/lib/messed/matcher.rb +50 -0
  34. data/lib/messed/message/twitter.rb +23 -0
  35. data/lib/messed/message.rb +35 -0
  36. data/lib/messed/queue/beanstalk.rb +56 -0
  37. data/lib/messed/queue.rb +17 -0
  38. data/lib/messed/session/memcache.rb +40 -0
  39. data/lib/messed/session.rb +7 -0
  40. data/lib/messed/tasks/console.rb +70 -0
  41. data/lib/messed/tasks/generation.rb +12 -0
  42. data/lib/messed/tasks/runner.rb +50 -0
  43. data/lib/messed/tasks/status.rb +37 -0
  44. data/lib/messed/tasks.rb +9 -0
  45. data/lib/messed/util/remote_status.rb +45 -0
  46. data/lib/messed.rb +235 -0
  47. data/patterns/messed/Pattern +24 -0
  48. data/patterns/messed/Rakefile +12 -0
  49. data/patterns/messed/app/runner.rb +5 -0
  50. data/patterns/messed/bin/console +3 -0
  51. data/patterns/messed/bin/runner +3 -0
  52. data/patterns/messed/bin/status +3 -0
  53. data/patterns/messed/bin/web +13 -0
  54. data/patterns/messed/config/environment.rb +15 -0
  55. data/patterns/messed/config/environments/development.rb +2 -0
  56. data/patterns/messed/config/environments/production.rb +0 -0
  57. data/patterns/messed/config/environments/test.rb +0 -0
  58. data/patterns/messed/spec/application_spec.rb +12 -0
  59. data/patterns/messed/spec/spec.opts +7 -0
  60. data/patterns/messed/spec/spec_helper.rb +44 -0
  61. data/spec/adapter/applications/twitter_search/app/application.rb +3 -0
  62. data/spec/adapter/applications/twitter_search/app/runner.rb +7 -0
  63. data/spec/adapter/applications/twitter_search/bin/incoming +89 -0
  64. data/spec/adapter/applications/twitter_search/bin/runner +21 -0
  65. data/spec/adapter/applications/twitter_search/bin/status +36 -0
  66. data/spec/adapter/applications/twitter_search/config/environment.rb +11 -0
  67. data/spec/adapter/applications/twitter_search/config/environments/development.rb +0 -0
  68. data/spec/adapter/applications/twitter_search/log/development.log +1006 -0
  69. data/spec/adapter/applications/twitter_sender/app/application.rb +0 -0
  70. data/spec/adapter/applications/twitter_sender/app/runner.rb +0 -0
  71. data/spec/adapter/applications/twitter_sender/bin/incoming +89 -0
  72. data/spec/adapter/applications/twitter_sender/bin/runner +21 -0
  73. data/spec/adapter/applications/twitter_sender/bin/status +36 -0
  74. data/spec/adapter/applications/twitter_sender/config/environment.rb +11 -0
  75. data/spec/adapter/applications/twitter_sender/config/environments/development.rb +0 -0
  76. data/spec/adapter/applications/twitter_sender/log/development.log +146 -0
  77. data/spec/adapter/http/direct_message +0 -0
  78. data/spec/adapter/http/twitter_search +20 -0
  79. data/spec/adapter/http/update +0 -0
  80. data/spec/adapter/twitter_search_spec.rb +36 -0
  81. data/spec/adapter/twitter_sender_spec.rb +64 -0
  82. data/spec/booter_spec.rb +7 -0
  83. data/spec/fixtures/booter/app/application.rb +3 -0
  84. data/spec/fixtures/booter/app/runner.rb +7 -0
  85. data/spec/fixtures/booter/bin/incoming +89 -0
  86. data/spec/fixtures/booter/bin/runner +21 -0
  87. data/spec/fixtures/booter/bin/status +36 -0
  88. data/spec/fixtures/booter/config/environment.rb +15 -0
  89. data/spec/fixtures/booter/config/environments/development.rb +0 -0
  90. data/spec/fixtures/booter/log/development.log +11 -0
  91. data/spec/message_spec.rb +52 -0
  92. data/spec/session_spec.rb +34 -0
  93. data/spec/spec.opts +7 -0
  94. data/spec/spec_helper.rb +8 -0
  95. data/test/app/runner.rb +5 -0
  96. data/test/config/environment.rb +15 -0
  97. data/test/config/environments/development.rb +2 -0
  98. data/test/config/environments/production.rb +0 -0
  99. data/test/config/environments/test.rb +0 -0
  100. data/test/spec/application_spec.rb +12 -0
  101. data/test/spec/spec_helper.rb +44 -0
  102. 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
@@ -0,0 +1,17 @@
1
+ require 'beanstalk-client'
2
+
3
+ class Messed
4
+ class Queue
5
+
6
+ autoload :Beanstalk, File.join('messed', 'queue', 'beanstalk')
7
+
8
+ def <<(message)
9
+ raise
10
+ end
11
+
12
+ def take
13
+ raise
14
+ end
15
+
16
+ end
17
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ class Messed
2
+ class Session
3
+
4
+ autoload :Memcache, File.join('messed', 'session', 'memcache')
5
+
6
+ end
7
+ end