mailman 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright (c) 2010 Jonathan Rudenberg
3
+ Copyright (c) 2010-2012 Jonathan Rudenberg
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Mailman
1
+ # Mailman [![Build Status](https://secure.travis-ci.org/titanous/mailman.png)](https://secure.travis-ci.org/titanous/mailman)
2
2
 
3
3
  Mailman is an incoming mail processing microframework (with POP3 and Maildir
4
4
  support), that works with Rails "out of the box".
@@ -12,10 +12,25 @@ support), that works with Rails "out of the box".
12
12
 
13
13
  See the [User Guide](http://rubydoc.info/github/titanous/mailman/master/file/USER_GUIDE.md) for more information.
14
14
 
15
+ There is also a great [Getting Started Guide](http://dansowter.com/mailman-guide/) written by Dan Sowter.
16
+
17
+
15
18
  ## Installation
16
19
 
17
20
  gem install mailman
18
21
 
22
+ ## Compatibility
23
+
24
+ Tested on all Ruby versions with Travis CI.
25
+
26
+ ### Dependencies
27
+
28
+ * mail >= 2.0.3
29
+ * activesupport >= 2.3.4
30
+ * listen >= 0.4.1
31
+ * maildir >= 0.5.0
32
+ * i18n >= 0.4.1
33
+
19
34
  ## Thanks
20
35
 
21
36
  This project was sponsored by the [Ruby Summer of Code](http://rubysoc.org),
@@ -23,11 +38,17 @@ and my mentor was [Steven Soroka](http://github.com/ssoroka).
23
38
 
24
39
  ### Contributors
25
40
 
26
- - [Tim Carey-Smith](http://github.com/halorgium)
27
41
  - [Nicolas Aguttes](http://github.com/tranquiliste)
42
+ - [Nathan Broadbent](https://github.com/ndbroadbent)
43
+ - [Tim Carey-Smith](http://github.com/halorgium)
44
+ - [Francis Chong](https://github.com/siuying)
45
+ - [Cyril Mougel](http://github.com/shingara)
46
+ - [Phillip Ridlen](https://github.com/philtr)
28
47
  - [Daniel Schierbeck](http://github.com/dasch)
48
+ - [Steven Soroka](http://github.com/ssoroka)
29
49
  - [Ian White](http://github.com/ianwhite)
30
50
 
51
+
31
52
  ## Copyright
32
53
 
33
- Copyright (c) 2010 Jonathan Rudenberg. See LICENSE for details.
54
+ Copyright (c) 2010-2012 Jonathan Rudenberg. See LICENSE for details.
data/USER_GUIDE.md CHANGED
@@ -99,9 +99,9 @@ All captures from matchers are available as block arguments:
99
99
  #### Class Routing
100
100
 
101
101
  Messages can also be routed to methods. For instance, to route to an
102
- ActionMailer mailer with a `receive` method defined, this will work:
102
+ Object with a `receive` instance method defined, this will work:
103
103
 
104
- from '%user%@example.org', SampleMailer
104
+ from '%user%@example.org', Sample
105
105
 
106
106
  Messages can also be routed to arbitrary instance methods:
107
107
 
@@ -242,3 +242,10 @@ loading will be disabled.
242
242
  interfere with running Mailman with cron or as a daemon.
243
243
 
244
244
  **Default**: `false`
245
+
246
+ ### Graceful death
247
+
248
+ `Mailman.config.graceful_death`, if set, will catch SIGINTs
249
+ (Control-C) and allow the mail receiver to finish its current
250
+ iteration before exiting. Note that this currently only works
251
+ with POP3 receivers.
data/lib/mailman.rb CHANGED
@@ -5,9 +5,6 @@ require 'active_support'
5
5
  require 'active_support/core_ext/string/inflections'
6
6
  require 'active_support/core_ext/hash/indifferent_access'
7
7
 
8
- require 'maildir'
9
- require 'fssm'
10
-
11
8
  require 'mailman/version'
12
9
 
13
10
  module Mailman
@@ -15,6 +15,8 @@ module Mailman
15
15
  attr_reader :processor
16
16
 
17
17
  # Creates a new router, and sets up any routes passed in the block.
18
+ # @param [Hash] options the application options
19
+ # @option options [true,false] :graceful_death catch interrupt signal and don't die until end of poll
18
20
  # @param [Proc] block a block with routes
19
21
  def initialize(&block)
20
22
  @router = Mailman::Router.new
@@ -22,6 +24,10 @@ module Mailman
22
24
  instance_eval(&block)
23
25
  end
24
26
 
27
+ def polling?
28
+ Mailman.config.poll_interval > 0 && !@polling_interrupt
29
+ end
30
+
25
31
  # Sets the block to run if no routes match a message.
26
32
  def default(&block)
27
33
  @router.default_block = block
@@ -32,52 +38,79 @@ module Mailman
32
38
  Mailman.logger.info "Mailman v#{Mailman::VERSION} started"
33
39
 
34
40
  rails_env = File.join(Mailman.config.rails_root, 'config', 'environment.rb')
35
- if Mailman.config.rails_root && File.exist?(rails_env)
41
+ if Mailman.config.rails_root && File.exist?(rails_env) && !(defined?(Rails) && Rails.env)
36
42
  Mailman.logger.info "Rails root found in #{Mailman.config.rails_root}, requiring environment..."
37
43
  require rails_env
38
44
  end
39
45
 
40
- if !Mailman.config.ignore_stdin && $stdin.fcntl(Fcntl::F_GETFL, 0) == 0 # we have stdin
46
+ if Mailman.config.graceful_death
47
+ # When user presses CTRL-C, finish processing current message before exiting
48
+ Signal.trap("INT") { @polling_interrupt = true }
49
+ end
50
+
51
+ # STDIN
52
+ if !Mailman.config.ignore_stdin && $stdin.fcntl(Fcntl::F_GETFL, 0) == 0
41
53
  Mailman.logger.debug "Processing message from STDIN."
42
54
  @processor.process($stdin.read)
55
+
56
+ # IMAP
57
+ elsif Mailman.config.imap
58
+ options = {:processor => @processor}.merge(Mailman.config.imap)
59
+ Mailman.logger.info "IMAP receiver enabled (#{options[:username]}@#{options[:server]})."
60
+ polling_loop Receiver::IMAP.new(options)
61
+
62
+ # POP3
43
63
  elsif Mailman.config.pop3
44
64
  options = {:processor => @processor}.merge(Mailman.config.pop3)
45
65
  Mailman.logger.info "POP3 receiver enabled (#{options[:username]}@#{options[:server]})."
46
- if Mailman.config.poll_interval > 0 # we should poll
47
- polling = true
48
- Mailman.logger.info "Polling enabled. Checking every #{Mailman.config.poll_interval} seconds."
49
- else
50
- polling = false
51
- Mailman.logger.info 'Polling disabled. Checking for messages once.'
52
- end
53
-
54
- connection = Receiver::POP3.new(options)
55
- loop do
56
- Mailman.logger.debug "Checking POP3 server for messages..."
57
- connection.connect
58
- connection.get_messages
59
- connection.disconnect
60
- break if !polling
61
- sleep Mailman.config.poll_interval
62
- end
66
+ polling_loop Receiver::POP3.new(options)
63
67
 
68
+ # Maildir
64
69
  elsif Mailman.config.maildir
70
+ require 'maildir'
71
+ require 'listen'
72
+
65
73
  Mailman.logger.info "Maildir receiver enabled (#{Mailman.config.maildir})."
66
- maildir = Maildir.new(Mailman.config.maildir)
74
+ @maildir = Maildir.new(Mailman.config.maildir)
67
75
 
68
- # Process messages queued in the new directory
69
- Mailman.logger.debug "Processing new message queue..."
70
- maildir.list(:new).each do |message|
71
- @processor.process_maildir_message(message)
76
+ Mailman.logger.debug "Monitoring the Maildir for new messages..."
77
+ Listen.to File.join(Mailman.config.maildir, 'new') do |modified, added, removed|
78
+ process_maildir
72
79
  end
80
+ end
81
+ end
73
82
 
74
- Mailman.logger.debug "Monitoring the Maildir for new messages..."
75
- FSSM.monitor File.join(Mailman.config.maildir, 'new') do |monitor|
76
- monitor.create { |directory, filename| # a new message was delivered to new
77
- message = Maildir::Message.new(maildir, "new/#{filename}")
78
- @processor.process_maildir_message(message)
79
- }
83
+ # List all message in Maildir new directory and process it
84
+ def process_maildir
85
+ # Process messages queued in the new directory
86
+ Mailman.logger.debug "Processing new message queue..."
87
+ @maildir.list(:new).each do |message|
88
+ @processor.process_maildir_message(message)
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ # Run the polling loop for the email inbox connection
95
+ def polling_loop(connection)
96
+ if polling?
97
+ polling_msg = "Polling enabled. Checking every #{Mailman.config.poll_interval} seconds."
98
+ else
99
+ polling_msg = "Polling disabled. Checking for messages once."
100
+ end
101
+ Mailman.logger.info(polling_msg)
102
+
103
+ loop do
104
+ begin
105
+ connection.connect
106
+ connection.get_messages
107
+ connection.disconnect
108
+ rescue SystemCallError => e
109
+ Mailman.logger.error e.message
80
110
  end
111
+
112
+ break unless polling?
113
+ sleep Mailman.config.poll_interval
81
114
  end
82
115
  end
83
116
 
@@ -5,7 +5,7 @@ module Mailman
5
5
  attr_accessor :logger
6
6
 
7
7
  # @return [Hash] the configuration hash for POP3
8
- attr_accessor :pop3
8
+ attr_accessor :pop3, :imap
9
9
 
10
10
  # @return [Fixnum] the poll interval for POP3 or IMAP. Setting this to 0
11
11
  # disables polling
@@ -18,10 +18,15 @@ module Mailman
18
18
  # rails environment loading
19
19
  attr_accessor :rails_root
20
20
 
21
- # @return [boolean] whether or not to ignore stdin. Setting this to true
21
+ # @return [boolean] whether or not to ignore stdin. Setting this to true
22
22
  # stops Mailman from entering stdin processing mode.
23
23
  attr_accessor :ignore_stdin
24
-
24
+
25
+ # @return [boolean] catch SIGINT and allow current iteration to finish
26
+ # rather than dropping dead immediately. Currently only works with POP3
27
+ # connections.
28
+ attr_accessor :graceful_death
29
+
25
30
  def logger
26
31
  @logger ||= Logger.new(STDOUT)
27
32
  end
@@ -13,16 +13,17 @@ module Mailman
13
13
  # router.
14
14
  # @param [String] message the message to process
15
15
  def process(message)
16
- message = Mail.new(message)
17
- Mailman.logger.info "Got new message from '#{message.from.first}' with subject '#{message.subject}'."
18
- @router.route(message)
16
+ mail = Mail.new(message)
17
+ from = mail.from.nil? ? "unknown" : mail.from.first
18
+ Mailman.logger.info "Got new message from '#{from}' with subject '#{mail.subject}'."
19
+ @router.route(mail)
19
20
  end
20
21
 
21
22
  # Processes a +Maildir::Message+ instance.
22
23
  def process_maildir_message(message)
24
+ process(message.data)
23
25
  message.process # move message to cur
24
26
  message.seen!
25
- process(message.data)
26
27
  end
27
28
 
28
29
  end
@@ -2,6 +2,7 @@ module Mailman
2
2
  module Receiver
3
3
 
4
4
  autoload :POP3, 'mailman/receiver/pop3'
5
+ autoload :IMAP, 'mailman/receiver/imap'
5
6
 
6
7
  end
7
- end
8
+ end
@@ -0,0 +1,54 @@
1
+ require 'net/imap'
2
+
3
+ module Mailman
4
+ module Receiver
5
+ # Receives messages using IMAP, and passes them to a {MessageProcessor}.
6
+ class IMAP
7
+
8
+ # @return [Net::IMAP] the IMAP connection
9
+ attr_reader :connection
10
+
11
+ # @param [Hash] options the receiver options
12
+ # @option options [MessageProcessor] :processor the processor to pass new
13
+ # messages to
14
+ # @option options [String] :server the server to connect to
15
+ # @option options [Integer] :port the port to connect to
16
+ # @option options [String] :username the username to authenticate with
17
+ # @option options [String] :password the password to authenticate with
18
+ def initialize(options)
19
+ @processor = options[:processor]
20
+ @username = options[:username]
21
+ @password = options[:password]
22
+ @filter = options[:filter] || ['NEW']
23
+ @port = options[:port] || 143
24
+
25
+ @connection = Net::IMAP.new(options[:server], @port)
26
+ end
27
+
28
+ # Connects to the IMAP server.
29
+ def connect
30
+ @connection.login(@username, @password)
31
+ @connection.examine("INBOX")
32
+ end
33
+
34
+ # Disconnects from the IMAP server.
35
+ def disconnect
36
+ @connection.logout
37
+ @connection.disconnected? ? true : @connection.disconnect rescue nil
38
+ end
39
+
40
+ # Iterates through new messages, passing them to the processor, and
41
+ # deleting them.
42
+ def get_messages
43
+ @connection.search(@filter).each do |message|
44
+ body = @connection.fetch(message,"RFC822")[0].attr["RFC822"]
45
+ @processor.process(body)
46
+ @connection.store(message,"+FLAGS",[Net::IMAP::DELETED])
47
+ end
48
+ # Clears messages that have the Deleted flag set
49
+ @connection.expunge
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -37,7 +37,15 @@ module Mailman
37
37
  # Matches against the Body of a message.
38
38
  class BodyCondition < Condition
39
39
  def match(message)
40
- @matcher.match(message.body.decoded)
40
+ if message.multipart?
41
+ message.parts.each do |part|
42
+ if result = @matcher.match(part.decoded)
43
+ return result
44
+ end
45
+ end
46
+ else
47
+ @matcher.match(message.body.decoded)
48
+ end
41
49
  end
42
50
  end
43
51
 
@@ -1,3 +1,3 @@
1
1
  module Mailman
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
metadata CHANGED
@@ -1,123 +1,94 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: mailman
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 4
8
- - 0
9
- version: 0.4.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Jonathan Rudenberg
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2010-10-03 00:00:00 -04:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-04-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: mail
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &70284016144160 !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 2
30
- - 0
31
- - 3
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
32
21
  version: 2.0.3
33
22
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: activesupport
37
23
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: *70284016144160
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70284016143180 !ruby/object:Gem::Requirement
39
28
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- segments:
44
- - 2
45
- - 3
46
- - 4
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
47
32
  version: 2.3.4
48
33
  type: :runtime
49
- version_requirements: *id002
50
- - !ruby/object:Gem::Dependency
51
- name: fssm
52
34
  prerelease: false
53
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *70284016143180
36
+ - !ruby/object:Gem::Dependency
37
+ name: listen
38
+ requirement: &70284016140380 !ruby/object:Gem::Requirement
54
39
  none: false
55
- requirements:
56
- - - ">="
57
- - !ruby/object:Gem::Version
58
- segments:
59
- - 0
60
- - 1
61
- - 4
62
- version: 0.1.4
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 0.4.1
63
44
  type: :runtime
64
- version_requirements: *id003
65
- - !ruby/object:Gem::Dependency
66
- name: maildir
67
45
  prerelease: false
68
- requirement: &id004 !ruby/object:Gem::Requirement
46
+ version_requirements: *70284016140380
47
+ - !ruby/object:Gem::Dependency
48
+ name: maildir
49
+ requirement: &70284017139220 !ruby/object:Gem::Requirement
69
50
  none: false
70
- requirements:
71
- - - ">="
72
- - !ruby/object:Gem::Version
73
- segments:
74
- - 0
75
- - 5
76
- - 0
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
77
54
  version: 0.5.0
78
55
  type: :runtime
79
- version_requirements: *id004
80
- - !ruby/object:Gem::Dependency
81
- name: i18n
82
56
  prerelease: false
83
- requirement: &id005 !ruby/object:Gem::Requirement
57
+ version_requirements: *70284017139220
58
+ - !ruby/object:Gem::Dependency
59
+ name: i18n
60
+ requirement: &70284017138760 !ruby/object:Gem::Requirement
84
61
  none: false
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- segments:
89
- - 0
90
- - 4
91
- - 1
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
92
65
  version: 0.4.1
93
66
  type: :runtime
94
- version_requirements: *id005
95
- - !ruby/object:Gem::Dependency
96
- name: rspec
97
67
  prerelease: false
98
- requirement: &id006 !ruby/object:Gem::Requirement
68
+ version_requirements: *70284017138760
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: &70284017138300 !ruby/object:Gem::Requirement
99
72
  none: false
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- segments:
104
- - 0
105
- version: "0"
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 2.6.0
106
77
  type: :development
107
- version_requirements: *id006
108
- description: Mailman makes it easy to process incoming emails with a simple routing DSL
109
- email:
78
+ prerelease: false
79
+ version_requirements: *70284017138300
80
+ description: Mailman makes it easy to process incoming emails with a simple routing
81
+ DSL
82
+ email:
110
83
  - jonathan@titanous.com
111
84
  executables: []
112
-
113
85
  extensions: []
114
-
115
86
  extra_rdoc_files: []
116
-
117
- files:
87
+ files:
118
88
  - lib/mailman/application.rb
119
89
  - lib/mailman/configuration.rb
120
90
  - lib/mailman/message_processor.rb
91
+ - lib/mailman/receiver/imap.rb
121
92
  - lib/mailman/receiver/pop3.rb
122
93
  - lib/mailman/receiver.rb
123
94
  - lib/mailman/route/condition.rb
@@ -133,37 +104,28 @@ files:
133
104
  - LICENSE
134
105
  - README.md
135
106
  - USER_GUIDE.md
136
- has_rdoc: true
137
107
  homepage: http://mailmanrb.com
138
108
  licenses: []
139
-
140
109
  post_install_message:
141
110
  rdoc_options: []
142
-
143
- require_paths:
111
+ require_paths:
144
112
  - lib
145
- required_ruby_version: !ruby/object:Gem::Requirement
113
+ required_ruby_version: !ruby/object:Gem::Requirement
146
114
  none: false
147
- requirements:
148
- - - ">="
149
- - !ruby/object:Gem::Version
150
- segments:
151
- - 0
152
- version: "0"
153
- required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
120
  none: false
155
- requirements:
156
- - - ">="
157
- - !ruby/object:Gem::Version
158
- segments:
159
- - 0
160
- version: "0"
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
161
125
  requirements: []
162
-
163
126
  rubyforge_project: mailman
164
- rubygems_version: 1.3.7
127
+ rubygems_version: 1.8.11
165
128
  signing_key:
166
129
  specification_version: 3
167
130
  summary: A incoming email processing microframework
168
131
  test_files: []
169
-