airbrush 0.0.2

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