Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

adding initial version of fetcher plugin

  • Loading branch information...
commit f12a925b477d5aec72c16acd1614a389fb8d4cf1 0 parents
@dweinand dweinand authored
4 README
@@ -0,0 +1,4 @@
+Fetcher
+=======
+
+Simplified message fetching
22 Rakefile
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the fetcher plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the fetcher plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'Fetcher'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
9 generators/fetcher_daemon/USAGE
@@ -0,0 +1,9 @@
+Description:
+ Create a fetcher daemon
+
+Example:
+ ./script/generate fetcher_daemon sms
+
+ This will create:
+ /config/sms_fetcher.yml
+ /script/sms_fetcher
9 generators/fetcher_daemon/fetcher_daemon_generator.rb
@@ -0,0 +1,9 @@
+class FetcherDaemonGenerator < Rails::Generator::NamedBase
+ def manifest
+ record do |m|
+ m.template 'config.yml', "config/#{file_name}.yml"
+ m.template 'daemon', "script/#{file_name}_fetcher", :chmod => 0755
+ m.template 'daemon.rb', "/lib/daemon.rb"
+ end
+ end
+end
14 generators/fetcher_daemon/templates/config.yml
@@ -0,0 +1,14 @@
+development:
+ server: localhost
+ username: username
+ password: password
+
+test:
+ server: localhost
+ username: username
+ password: password
+
+production:
+ server: localhost
+ username: username
+ password: password
25 generators/fetcher_daemon/templates/daemon
@@ -0,0 +1,25 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'daemon'
+require 'yaml'
+require 'fetcher'
+
+class <%=class_name%>FetcherDaemon < Daemon::Base
+
+ @config = YAML.load_file("#{RAILS_ROOT}/config/<%=file_name%>_fetcher.yml")
+ @config = @config[RAILS_ENV].to_options
+
+ @sleep_time = @config.delete(:sleep_time)
+ def self.start
+ puts "Starting <%=class_name%>Fetcher"
+ # Add your own receiver object below and specify fetcher subclass
+ @fetcher = Fetcher::Base.new({:receiver => nil}.merge(@config))
+
+ loop do
+ @fetcher.fetch
+ sleep(@sleep_time)
+ end
+ end
+
+end
+
69 generators/fetcher_daemon/templates/daemon.rb
@@ -0,0 +1,69 @@
+# Taken from http://snippets.dzone.com/posts/show/2265
+
+require 'fileutils'
+
+module Daemon
+ WorkingDirectory = File.expand_path(File.dirname(__FILE__))
+
+ class Base
+ def self.pid_fn
+ File.join(WorkingDirectory, "#{name}.pid")
+ end
+
+ def self.daemonize
+ Controller.daemonize(self)
+ end
+ end
+
+ module PidFile
+ def self.store(daemon, pid)
+ File.open(daemon.pid_fn, 'w') {|f| f << pid}
+ end
+
+ def self.recall(daemon)
+ IO.read(daemon.pid_fn).to_i rescue nil
+ end
+ end
+
+ module Controller
+ def self.daemonize(daemon)
+ case !ARGV.empty? && ARGV[0]
+ when 'start'
+ start(daemon)
+ when 'stop'
+ stop(daemon)
+ when 'restart'
+ stop(daemon)
+ start(daemon)
+ else
+ puts "Invalid command. Please specify start, stop or restart."
+ exit
+ end
+ end
+
+ def self.start(daemon)
+ fork do
+ Process.setsid
+ exit if fork
+ PidFile.store(daemon, Process.pid)
+ Dir.chdir WorkingDirectory
+ File.umask 0000
+ STDIN.reopen "/dev/null"
+ STDOUT.reopen "/dev/null", "a"
+ STDERR.reopen STDOUT
+ trap("TERM") {daemon.stop; exit}
+ daemon.start
+ end
+ end
+
+ def self.stop(daemon)
+ if !File.file?(daemon.pid_fn)
+ puts "Pid file not found. Is the daemon started?"
+ exit
+ end
+ pid = PidFile.recall(daemon)
+ FileUtils.rm(daemon.pid_fn)
+ pid && Process.kill("TERM", pid)
+ end
+ end
+end
0  init.rb
No changes.
1  install.rb
@@ -0,0 +1 @@
+# Install hook code here
3  lib/fetcher.rb
@@ -0,0 +1,3 @@
+require 'fetcher/base'
+require 'fetcher/pop'
+require 'fetcher/imap'
23 lib/fetcher/base.rb
@@ -0,0 +1,23 @@
+module Fetcher
+ class Base
+
+ def initialize(options={})
+ %w(server username password receiver).each do |opt|
+ instance_eval("@#{opt} = options[:#{opt}]")
+ end
+ end
+
+ def fetch
+ establish_connection
+ get_message
+ close_connection
+ end
+
+ protected
+
+ def establish_connection; true; end
+ def get_message; true; end
+ def close_connection; true; end
+
+ end
+end
42 lib/fetcher/imap.rb
@@ -0,0 +1,42 @@
+require File.dirname(__FILE__) + '/../vendor/plain_imap'
+
+module Fetcher
+ class Imap < Base
+
+ def initialize(options={})
+ @authentication = options.delete(:authentication)
+ super(options)
+ end
+
+ protected
+
+ def establish_connection
+ @connection = Net::IMAP.new(@server)
+ @connection.authenticate((@authentication || 'PLAIN'), @username, @password)
+ end
+
+ def get_message
+ @connection.select('INBOX')
+ @connection.search(['ALL']).each do |message_id|
+ msg = @connection.fetch(message_id,'RFC822')[0].attr['RFC822']
+ # process the email message
+ begin
+ @receiver.receive(msg)
+ rescue
+ # Store the message for inspection if the receiver errors
+ @connection.append('bogus', msg)
+ end
+ # Mark message as deleted
+ @connection.store(message_id, "+FLAGS", [:Deleted])
+ end
+ end
+
+ def close_connection
+ # expunge messages and log out.
+ @connection.expunge
+ @connection.logout
+ @connection.disconnect
+ end
+
+ end
+end
37 lib/fetcher/pop.rb
@@ -0,0 +1,37 @@
+require File.dirname(__FILE__) + '/../vendor/secure_pop'
+
+module Fetcher
+ class Pop < Base
+
+ def initialize(options={})
+ @ssl = options.delete(:ssl)
+ super(options)
+ end
+
+ protected
+
+ def establish_connection
+ @connection = Net::POP3.new(@server)
+ @connection.enable_ssl(OpenSSL::SSL::VERIFY_NONE) if @ssl
+ @connection.start(@username, @password)
+ end
+
+ def get_message
+ unless @connection.mails.empty?
+ @connection.each_mail do |msg|
+ begin
+ @receiver.receive(msg.pop)
+ rescue
+ # Store the message for inspection if the receiver errors
+ end
+ msg.delete
+ end
+ end
+ end
+
+ def close_connection
+ @connection.finish
+ end
+
+ end
+end
18 lib/vendor/plain_imap.rb
@@ -0,0 +1,18 @@
+# add plain as an authentication type...
+# This is taken from:
+# http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/lib/net/imap.rb?revision=7657&view=markup&pathrev=10966
+
+# Authenticator for the "PLAIN" authentication type. See
+# #authenticate().
+class PlainAuthenticator
+ def process(data)
+ return "\0#{@user}\0#{@password}"
+ end
+
+ private
+
+ def initialize(user, password)
+ @user = user
+ @password = password
+ end
+end
125 lib/vendor/secure_pop.rb
@@ -0,0 +1,125 @@
+require 'socket'
+require 'net/pop'
+require 'net/protocol'
+require 'openssl/ssl'
+
+# Backport of ruby 1.9's POP3 SSL support
+class Net::POP3
+ @@usessl = nil
+ @@verify = nil
+ @@certs = nil
+ PORT = 110
+ SSL_PORT = 995
+
+ def self.default_port
+ PORT
+ end
+
+ def self.default_ssl_port
+ SSL_PORT
+ end
+
+ # Enable SSL for all new instances.
+ # +verify+ is the type of verification to do on the Server Cert; Defaults
+ # to OpenSSL::SSL::VERIFY_PEER.
+ # +certs+ is a file or directory holding CA certs to use to verify the
+ # server cert; Defaults to nil.
+ def self.enable_ssl( verify = OpenSSL::SSL::VERIFY_PEER, certs = nil )
+ @@usessl = true
+ @@verify = verify
+ @@certs = certs
+ end
+
+ # Disable SSL for all new instances.
+ def self.disable_ssl
+ @@usessl = nil
+ @@verify = nil
+ @@certs = nil
+ end
+
+ # Creates a new POP3 object.
+ # +addr+ is the hostname or ip address of your POP3 server.
+ # The optional +port+ is the port to connect to.
+ # The optional +isapop+ specifies whether this connection is going
+ # to use APOP authentication; it defaults to +false+.
+ # This method does *not* open the TCP connection.
+ def initialize(addr, port = nil, isapop = false)
+ @address = addr
+ @usessl = @@usessl
+ if @usessl
+ @port = port || SSL_PORT
+ else
+ @port = port || PORT
+ end
+ @apop = isapop
+
+ @certs = @@certs
+ @verify = @@verify
+
+ @command = nil
+ @socket = nil
+ @started = false
+ @open_timeout = 30
+ @read_timeout = 60
+ @debug_output = nil
+
+ @mails = nil
+ @n_mails = nil
+ @n_bytes = nil
+ end
+
+ # does this instance use SSL?
+ def usessl?
+ @usessl
+ end
+
+ # Enables SSL for this instance. Must be called before the connection is
+ # established to have any effect.
+ # +verify+ is the type of verification to do on the Server Cert; Defaults
+ # to OpenSSL::SSL::VERIFY_PEER.
+ # +certs+ is a file or directory holding CA certs to use to verify the
+ # server cert; Defaults to nil.
+ # +port+ is port to establish the SSL conection on; Defaults to 995.
+ def enable_ssl(verify = OpenSSL::SSL::VERIFY_PEER, certs = nil,
+ port = SSL_PORT)
+ @usessl = true
+ @verify = verify
+ @certs = certs
+ @port = port
+ end
+
+ def disable_ssl
+ @usessl = nil
+ @verify = nil
+ @certs = nil
+ end
+
+ def do_start( account, password )
+ s = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
+ if @usessl
+ unless defined?(OpenSSL)
+ raise "SSL extension not installed"
+ end
+ sslctx = OpenSSL::SSL::SSLContext.new
+ sslctx.verify_mode = @verify
+ sslctx.ca_file = @certs if @certs && FileTest::file?(@certs)
+ sslctx.ca_path = @certs if @certs && FileTest::directory?(@certs)
+ s = OpenSSL::SSL::SSLSocket.new(s, sslctx)
+ s.sync_close = true
+ s.connect
+ end
+
+ @socket = Net::InternetMessageIO.new(s)
+ on_connect
+ @command = Net::POP3Command.new(@socket)
+ if apop?
+ @command.apop account, password
+ else
+ @command.auth account, password
+ end
+ @started = true
+ ensure
+ do_finish if not @started
+ end
+ private :do_start
+end
4 tasks/fetcher_tasks.rake
@@ -0,0 +1,4 @@
+# desc "Explaining what the task does"
+# task :fetcher do
+# # Task goes here
+# end
26 test/fetcher_test.rb
@@ -0,0 +1,26 @@
+require File.dirname(__FILE__) + '/../../../../config/boot'
+require 'test/unit'
+require 'mocha'
+require 'fetcher'
+
+class FetcherTest < Test::Unit::TestCase
+
+ def setup
+ @receiver = mock()
+ @fetcher = Fetcher::Base.new(:server => 'test.host',
+ :username => 'name',
+ :password => 'password',
+ :receiver => @receiver)
+ end
+
+ def test_should_set_configuration_instance_variables
+ assert_equal 'test.host', @fetcher.instance_variable_get(:@server)
+ assert_equal 'name', @fetcher.instance_variable_get(:@username)
+ assert_equal 'password', @fetcher.instance_variable_get(:@password)
+ assert_equal @receiver, @fetcher.instance_variable_get(:@receiver)
+ end
+
+ def test_should_fetch_message
+ assert @fetcher.fetch
+ end
+end
1  uninstall.rb
@@ -0,0 +1 @@
+# Uninstall hook code here
Please sign in to comment.
Something went wrong with that request. Please try again.