airbrush 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +55 -0
  3. data/README.txt +39 -0
  4. data/Rakefile +4 -0
  5. data/bin/airbrush +61 -0
  6. data/bin/airbrush-example-client +60 -0
  7. data/config/hoe.rb +71 -0
  8. data/config/requirements.rb +17 -0
  9. data/lib/airbrush/client.rb +53 -0
  10. data/lib/airbrush/core_ext/get_args.rb +94 -0
  11. data/lib/airbrush/core_ext/timestamped_logger.rb +10 -0
  12. data/lib/airbrush/handler.rb +18 -0
  13. data/lib/airbrush/listeners/listener.rb +45 -0
  14. data/lib/airbrush/listeners/memcache.rb +53 -0
  15. data/lib/airbrush/listeners/socket.rb +0 -0
  16. data/lib/airbrush/listeners/webservice.rb +0 -0
  17. data/lib/airbrush/processors/image/image_processor.rb +31 -0
  18. data/lib/airbrush/processors/image/profiles/cmyk-profile.icc +0 -0
  19. data/lib/airbrush/processors/image/profiles/srgb-profile.icc +0 -0
  20. data/lib/airbrush/processors/image/rmagick.rb +116 -0
  21. data/lib/airbrush/processors/processor.rb +43 -0
  22. data/lib/airbrush/publishers/http.rb +13 -0
  23. data/lib/airbrush/publishers/memcache.rb +24 -0
  24. data/lib/airbrush/publishers/publisher.rb +16 -0
  25. data/lib/airbrush/server.rb +20 -0
  26. data/lib/airbrush/version.rb +9 -0
  27. data/lib/airbrush.rb +30 -0
  28. data/log/debug.log +0 -0
  29. data/script/destroy +14 -0
  30. data/script/generate +14 -0
  31. data/script/txt2html +74 -0
  32. data/setup.rb +1585 -0
  33. data/spec/airbrush/client_spec.rb +87 -0
  34. data/spec/airbrush/core_ext/get_args_spec.rb +0 -0
  35. data/spec/airbrush/handler_spec.rb +44 -0
  36. data/spec/airbrush/listeners/listener_spec.rb +18 -0
  37. data/spec/airbrush/listeners/memcache_spec.rb +131 -0
  38. data/spec/airbrush/processors/image/image_processor_spec.rb +56 -0
  39. data/spec/airbrush/processors/image/rmagick_spec.rb +179 -0
  40. data/spec/airbrush/processors/processor_spec.rb +110 -0
  41. data/spec/airbrush/publishers/memcache_spec.rb +46 -0
  42. data/spec/airbrush/publishers/publisher_spec.rb +17 -0
  43. data/spec/airbrush/server_spec.rb +57 -0
  44. data/spec/airbrush_spec.rb +9 -0
  45. data/spec/spec.opts +1 -0
  46. data/spec/spec_helper.rb +10 -0
  47. data/tasks/deployment.rake +34 -0
  48. data/tasks/environment.rake +7 -0
  49. data/tasks/rspec.rake +36 -0
  50. data/tasks/website.rake +17 -0
  51. data/website/index.html +11 -0
  52. data/website/index.txt +39 -0
  53. data/website/javascripts/rounded_corners_lite.inc.js +285 -0
  54. data/website/stylesheets/screen.css +138 -0
  55. data/website/template.rhtml +48 -0
  56. metadata +161 -0
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-01-22
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,55 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/airbrush
6
+ bin/airbrush-example-client
7
+ config/hoe.rb
8
+ config/requirements.rb
9
+ lib/airbrush.rb
10
+ lib/airbrush/client.rb
11
+ lib/airbrush/core_ext/get_args.rb
12
+ lib/airbrush/core_ext/timestamped_logger.rb
13
+ lib/airbrush/handler.rb
14
+ lib/airbrush/listeners/listener.rb
15
+ lib/airbrush/listeners/memcache.rb
16
+ lib/airbrush/listeners/socket.rb
17
+ lib/airbrush/listeners/webservice.rb
18
+ lib/airbrush/processors/image/image_processor.rb
19
+ lib/airbrush/processors/image/profiles/cmyk-profile.icc
20
+ lib/airbrush/processors/image/profiles/srgb-profile.icc
21
+ lib/airbrush/processors/image/rmagick.rb
22
+ lib/airbrush/processors/processor.rb
23
+ lib/airbrush/publishers/http.rb
24
+ lib/airbrush/publishers/memcache.rb
25
+ lib/airbrush/publishers/publisher.rb
26
+ lib/airbrush/server.rb
27
+ lib/airbrush/version.rb
28
+ log/debug.log
29
+ script/destroy
30
+ script/generate
31
+ script/txt2html
32
+ setup.rb
33
+ spec/airbrush/client_spec.rb
34
+ spec/airbrush/core_ext/get_args_spec.rb
35
+ spec/airbrush/handler_spec.rb
36
+ spec/airbrush/listeners/listener_spec.rb
37
+ spec/airbrush/listeners/memcache_spec.rb
38
+ spec/airbrush/processors/image/image_processor_spec.rb
39
+ spec/airbrush/processors/image/rmagick_spec.rb
40
+ spec/airbrush/processors/processor_spec.rb
41
+ spec/airbrush/publishers/memcache_spec.rb
42
+ spec/airbrush/publishers/publisher_spec.rb
43
+ spec/airbrush/server_spec.rb
44
+ spec/airbrush_spec.rb
45
+ spec/spec.opts
46
+ spec/spec_helper.rb
47
+ tasks/deployment.rake
48
+ tasks/environment.rake
49
+ tasks/rspec.rake
50
+ tasks/website.rake
51
+ website/index.html
52
+ website/index.txt
53
+ website/javascripts/rounded_corners_lite.inc.js
54
+ website/stylesheets/screen.css
55
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,39 @@
1
+ = AIRBRUSH
2
+
3
+ == DESCRIPTION
4
+
5
+ Airbrush is a distributed processing host for performing arbitrary tasks on a particular server.
6
+ Currently it supports using MemCache as the transport queue to communicate between an airbrush
7
+ server issuing a command and the server processing it.
8
+
9
+ Due to the distributed nature of MemCache, multiple Airbrush servers can be started across
10
+ many hosts, and each will monitor the MemCache incoming queue for results independently and
11
+ atomically.
12
+
13
+ Remote jobs are performed via a 'processor' installed as part of Airbrush, and is currently
14
+ configured in code in the server.rb source file (look for the handler definition/configuration). A
15
+ future enhancemnt could support automatic processor registration and/or a plugin architecture.
16
+
17
+ Actual communication between client and server via MemCache is done via a hash, that includes
18
+ a :command value, optional :args value and a uniqie :id value. The :id value is used to uniquely
19
+ identify this job, :command names the actual command to run on the remote server, and :params
20
+ is a hash of parameters that are passed to the command being executed.
21
+
22
+ Several conventions are used, the name of the command needs to match a method name on the
23
+ processor. The key names in the params hash need to match parameter names to the method being
24
+ invoked (and will be automatically assigned). The :id value is used to name a queue that the
25
+ client can poll for return values from the remote command, if required.
26
+
27
+ == USAGE
28
+
29
+ To use Airbrush, you will need a starling server installed and running:
30
+
31
+ $> starling -v -P /tmp/starling.pid -q /tmp/queue
32
+
33
+ and at least one Airbrush instance running, including your processor:
34
+
35
+ $> airbrush -v
36
+
37
+ To send a remote job to the Airbrush server, use the Airbrush::Client module located
38
+ in airbrush/client.rb. An example is airbrush-example-client, which sends an image to the
39
+ remote server to be turned into a set of previews.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/bin/airbrush ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created on 2008-1-22.
4
+ # Copyright (c) 2008. All rights reserved.
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError
9
+ # no rubygems to load, so we fail silently
10
+ end
11
+
12
+ require 'optparse'
13
+
14
+ OPTIONS = {
15
+ :memcache => '127.0.0.1:22122',
16
+ :frequency => 2,
17
+ :verbose => false
18
+ }
19
+ MANDATORY_OPTIONS = %w( )
20
+
21
+ parser = OptionParser.new do |opts|
22
+ opts.banner = <<BANNER
23
+ Airbrush is a dedicated job queue based image processor. Commands are added to the queue via memcache, and results posted back
24
+ via memcache. This portion of airbrush is the server component, please see the examples are of the gem for how to post and
25
+ receive results using an example client.
26
+
27
+ Usage: #{File.basename($0)} [options]
28
+
29
+ Options are:
30
+ BANNER
31
+ opts.separator ""
32
+ opts.on("-m", "--memcache=HOST", String,
33
+ "The address of the memcache host to connect to",
34
+ "Default: #{OPTIONS[:memcache]}") { |OPTIONS[:memcache]| }
35
+ opts.on("-f", "--frequency=SECONDS", String,
36
+ "How often the remote memcache server is polled for incoming jobs, units of seconds",
37
+ "Default: #{OPTIONS[:frequency]}") { |OPTIONS[:frequency]| }
38
+ opts.on("-l", "--log=file", String,
39
+ "Specify where to send logging output",
40
+ "Default: stdout") { |OPTIONS[:log_target]| }
41
+ opts.on("-v", "--verbose",
42
+ "Verbose output") { |OPTIONS[:verbose]| }
43
+ opts.on("-h", "--help",
44
+ "Show this help message.") { puts opts; exit }
45
+ opts.parse!(ARGV)
46
+
47
+ if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
48
+ puts opts; exit
49
+ end
50
+ end
51
+
52
+ # all good, lets go
53
+
54
+ require 'airbrush'
55
+ require 'daemons'
56
+
57
+ # Become a daemon
58
+ #Daemons.daemonize
59
+
60
+ server = Airbrush::Server.new(OPTIONS)
61
+ server.start
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created on 2008-1-22.
4
+ # Copyright (c) 2008. All rights reserved.
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError
9
+ # no rubygems to load, so we fail silently
10
+ end
11
+
12
+ require 'optparse'
13
+
14
+ OPTIONS = {
15
+ :memcache => '127.0.0.1:22122',
16
+ :verbose => false
17
+ }
18
+ MANDATORY_OPTIONS = %w( image output )
19
+
20
+ parser = OptionParser.new do |opts|
21
+ opts.banner = <<BANNER
22
+ Example client for accessing the resize operation on a remote airbrush server, queued via memcache.
23
+
24
+ Usage: #{File.basename($0)} [options]
25
+
26
+ Options are:
27
+ BANNER
28
+ opts.separator ""
29
+ opts.on("-m", "--memcache=HOST", String,
30
+ "The address of the memcache host to connect to",
31
+ "Default: #{OPTIONS[:memcache]}") { |OPTIONS[:memcache]| }
32
+ opts.on("-i", "--image=FILENAME", String,
33
+ "Source input image") { |OPTIONS[:image]| }
34
+ opts.on("-o", "--output=FILENAME", String,
35
+ "Target output image") { |OPTIONS[:output]| }
36
+ opts.on("-v", "--verbose",
37
+ "Verbose output") { |OPTIONS[:verbose]| }
38
+ opts.on("-h", "--help",
39
+ "Show this help message.") { puts opts; exit }
40
+ opts.parse!(ARGV)
41
+
42
+ if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
43
+ puts opts; exit
44
+ end
45
+ end
46
+
47
+ raise "Source image does not exist: #{OPTIONS[:image]}" unless File.exists?(OPTIONS[:image])
48
+
49
+ # all good, lets go!
50
+
51
+ require 'airbrush'
52
+
53
+ client = Airbrush::Client.new(OPTIONS[:memcache])
54
+ puts "Sending #{OPTIONS[:image]} for preview processing"
55
+ results = client.process('generate-previews', :previews, :image => File.read(OPTIONS[:image]), :filename => OPTIONS[:image], :sizes => {:small => [300], :large => [600]})
56
+ results.each do |k,v|
57
+ next if k == :original
58
+ File.open("#{OPTIONS[:output]}-#{k}.jpg", 'w') { |f| f << v[:image] }
59
+ puts "Saved preview #{OPTIONS[:output]}-#{k}.jpg (#{v[:width]}x#{v[:height]})" if OPTIONS[:verbose]
60
+ end
data/config/hoe.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'airbrush/version'
2
+
3
+ AUTHOR = 'FIXME full name' # can also be an array of Authors
4
+ EMAIL = "FIXME email"
5
+ DESCRIPTION = "description of gem"
6
+ GEM_NAME = 'airbrush' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'airbrush' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Airbrush::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'airbrush documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.author = AUTHOR
52
+ p.description = DESCRIPTION
53
+ p.email = EMAIL
54
+ p.summary = DESCRIPTION
55
+ p.url = HOMEPATH
56
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
57
+ p.test_globs = ["test/**/test_*.rb"]
58
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
59
+
60
+ # == Optional
61
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
62
+ p.extra_deps = [ [ 'starling', '>= 0.9.3' ], [ 'activesupport', '>= 2.0.2' ], [ 'ruby2ruby', '>= 1.1.8' ], [ 'daemons', '>= 1.0.9' ] ] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
63
+
64
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
65
+
66
+ end
67
+
68
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
69
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
70
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
71
+ hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'airbrush'
@@ -0,0 +1,53 @@
1
+ require 'starling'
2
+ require 'timeout'
3
+
4
+ module Airbrush
5
+ include Timeout
6
+ class AirbrushProcessingError < StandardError; end
7
+
8
+ class Client
9
+ DEFAULT_INCOMING_QUEUE = 'airbrush_incoming_queue'
10
+ DEFAULT_RESPONSE_TIMEOUT = 2.minutes
11
+ DEFAULT_QUEUE_VALIDITY = 0 # 10.minutes for the moment
12
+
13
+ attr_reader :host, :incoming_queue, :response_timeout, :queue_validity
14
+
15
+ def initialize(host, incoming_queue = DEFAULT_INCOMING_QUEUE, response_timeout = DEFAULT_RESPONSE_TIMEOUT, queue_validity = DEFAULT_QUEUE_VALIDITY)
16
+ @host = host
17
+ @server = Starling.new(@host)
18
+ @incoming_queue = incoming_queue
19
+ @response_timeout = response_timeout
20
+ @queue_validity = queue_validity.to_i
21
+ end
22
+
23
+ def process(id, command, args = {})
24
+ raise 'No job id specified' unless id
25
+ raise 'No command specified' unless command
26
+ raise "Invalid arguments #{args}" unless args.is_a? Hash
27
+
28
+ send_and_receive(id, command, args)
29
+ end
30
+
31
+ private
32
+
33
+ def send_and_receive(id, command, args)
34
+ @server.set(@incoming_queue, { :id => id, :command => command, :args => args }, @queue_validity, false)
35
+ queue = unique_name(id)
36
+ Timeout::timeout(@response_timeout) do
37
+ response = @server.get(queue)
38
+ raise AirbrushProcessingError, format_error(response) if response.include? :exception
39
+ return response
40
+ end
41
+ end
42
+
43
+ # REVISIT: share implementation with server?
44
+ def unique_name(id)
45
+ id.to_s
46
+ end
47
+
48
+ def format_error(response)
49
+ "#{response[:exception]}: #{response[:message]}"
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,94 @@
1
+ require 'ruby2ruby'
2
+ require 'parse_tree'
3
+
4
+ class ParseTreeArray < Array #:nodoc:
5
+ def self.translate(*args)
6
+ sexp = ::ParseTree.translate(*args)
7
+ # ParseTree.translate returns [nil] if called on an inherited method, so walk
8
+ # up the inheritance chain to find the class that the method was defined in
9
+ unless sexp.first
10
+ klass = args.first.ancestors.detect do |klass|
11
+ klass.public_instance_methods(false).include?(args.last.to_s)
12
+ end
13
+ sexp = ::ParseTree.translate(klass, args.last) if klass
14
+ end
15
+ self.new(sexp)
16
+ end
17
+
18
+ def deep_array_node(type = nil)
19
+ each do |node|
20
+ return ParseTreeArray.new(node) if node.is_a?(Array) && (!type || node[0] == type)
21
+ next unless node.is_a?(Array)
22
+ return ParseTreeArray.new(node).deep_array_node(type)
23
+ end
24
+ nil
25
+ end
26
+
27
+ def arg_nodes
28
+ self[1..-1].inject([]) do |sum,item|
29
+ sum << [item] unless item.is_a?(Array)
30
+ sum
31
+ end
32
+ end
33
+
34
+ def get_args
35
+ if arg_node = deep_array_node(:args)
36
+ # method defined with def keyword
37
+ args = arg_node.arg_nodes
38
+ default_node = arg_node.deep_array_node(:block)
39
+ return args unless default_node
40
+ else
41
+ # assuming method defined with Module#define_method
42
+ return []
43
+ end
44
+
45
+ # if it was defined with def, and we found the default_node,
46
+ # that should bring us back to regularly scheduled programming..
47
+
48
+ lasgns = default_node[1..-1]
49
+ lasgns.each do |asgn|
50
+ args.assoc(asgn[1]) << eval(RubyToRuby.new.process(asgn[2]))
51
+ end
52
+ args
53
+ end
54
+
55
+ end
56
+
57
+ # Used in mapping controller arguments to the params hash.
58
+ # NOTE: You must have the 'ruby2ruby' gem installed for this to work.
59
+ #
60
+ # ==== Examples
61
+ # # In PostsController
62
+ # def show(id) #=> id is the same as params[:id]
63
+ module GetArgs
64
+
65
+ # ==== Returns
66
+ # Array:: Method arguments and their default values.
67
+ #
68
+ # ==== Examples
69
+ # class Example
70
+ # def hello(one,two="two",three)
71
+ # end
72
+ #
73
+ # def goodbye
74
+ # end
75
+ # end
76
+ #
77
+ # Example.instance_method(:hello).get_args
78
+ # #=> [[:one], [:two, "two"], [:three, "three"]]
79
+ # Example.instance_method(:goodbye).get_args #=> nil
80
+ def get_args
81
+ klass, meth = self.to_s.split(/ /).to_a[1][0..-2].split("#")
82
+ # Remove stupidity for #<Method: Class(Object)#foo>
83
+ klass = $` if klass =~ /\(/
84
+ ParseTreeArray.translate(Object.const_get(klass), meth).get_args
85
+ end
86
+ end
87
+
88
+ class UnboundMethod #:nodoc:
89
+ include GetArgs
90
+ end
91
+
92
+ class Method #:nodoc:
93
+ include GetArgs
94
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveSupport
2
+ class BufferedLogger
3
+
4
+ def add_with_timestamps(severity, message = nil, progname = nil, &block)
5
+ add_without_timestamps(severity, "#{Time.now}: #{message}", progname, &block)
6
+ end
7
+
8
+ alias_method_chain :add, :timestamps
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module Airbrush
2
+ class Handler
3
+ attr_reader :processor, :publisher
4
+
5
+ def initialize(processor, publisher)
6
+ raise ArgumentError, 'no processor specified' unless processor
7
+ raise ArgumentError, 'no publisher specified' unless publisher
8
+
9
+ @processor = processor
10
+ @publisher = publisher
11
+ end
12
+
13
+ def process(id, command, args)
14
+ @publisher.publish id, @processor.dispatch(command, args)
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ module Airbrush
2
+ module Listeners
3
+ class Listener
4
+ attr_accessor :handler
5
+
6
+ def start
7
+ raise 'implementations provide concrete listener functionality'
8
+ end
9
+
10
+ protected
11
+
12
+ def process(op)
13
+ raise 'No operation specified' unless op
14
+
15
+ unless valid?(op)
16
+ log.error "Received invalid job #{op}"
17
+ return
18
+ end
19
+
20
+ log.debug "Processing #{op[:id]}"
21
+ start = Time.now
22
+
23
+ begin
24
+ @handler.process op[:id], op[:command], op[:args]
25
+ rescue Exception => e
26
+ log.error 'Received error during handler'
27
+ log.error e
28
+ ensure
29
+ log.debug "Processed #{op[:id]}: #{Time.now - start} seconds processing time"
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def valid?(op)
36
+ return false unless op.is_a? Hash
37
+ return false unless op[:id]
38
+ return false unless op[:command]
39
+ return false unless op[:args]
40
+ true
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,53 @@
1
+ require 'memcache'
2
+
3
+ module Airbrush
4
+ module Listeners
5
+ class Memcache < Listener
6
+ DEFAULT_POLL_FREQUENCY = 2 # seconds
7
+ DEFAULT_INCOMING_QUEUE = 'airbrush_incoming_queue'
8
+
9
+ attr_reader :host, :poll_frequency
10
+
11
+ def initialize(host, frequency = DEFAULT_POLL_FREQUENCY)
12
+ @host = host
13
+ @poll_frequency = frequency
14
+ catch_signals(:int)
15
+ end
16
+
17
+ def start
18
+ @running = true
19
+ @starling = MemCache.new(@host)
20
+
21
+ log.debug 'Accepting incoming jobs'
22
+
23
+ loop do
24
+ poll do |operation|
25
+ process operation
26
+ end
27
+
28
+ break unless @running
29
+ sleep @poll_frequency
30
+ end
31
+
32
+ @starling.reset
33
+ end
34
+
35
+ private
36
+
37
+ def poll
38
+ operation = @starling.get(DEFAULT_INCOMING_QUEUE)
39
+ yield operation if operation and block_given?
40
+ end
41
+
42
+ def catch_signals(*signals)
43
+ signals.each do |signal|
44
+ sig = signal.to_s.upcase
45
+ Signal.trap(sig) do
46
+ @running = false
47
+ log.debug "Intercepted SIG#{sig}, exiting"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
File without changes
File without changes
@@ -0,0 +1,31 @@
1
+ module Airbrush
2
+ module Processors
3
+ module Image
4
+ class ImageProcessor < Processor
5
+ class_inheritable_accessor :before_filters, :after_filters
6
+
7
+ def self.before_filter(*symbols)
8
+ self.before_filters = symbols
9
+ end
10
+
11
+ def self.after_filter(*symbols)
12
+ self.after_filters = symbols
13
+ end
14
+
15
+ def dispatch(command, args)
16
+ self.before_filters.each { |filter| filter_dispatch(filter, args) } if self.before_filters
17
+ rv = super command, args
18
+ self.after_filters.each { |filter| filter_dispatch(filter, args) } if self.after_filters
19
+ rv
20
+ end
21
+
22
+ def filter_dispatch(command, args)
23
+ raise "Unknown processor operation #{command} (#{args.inspect unless args.blank?})" unless self.respond_to? command
24
+ params = assign(command, args)
25
+ self.send command, *params
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end