mailman 0.4.0 → 0.5.0

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