mail_room 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c5a6b5950db35515bea9f2f82bdde5e7dd1ff367
4
- data.tar.gz: d051cde6554df19f74cea7054b94673c5fb0a718
3
+ metadata.gz: 645c66492e5e0a58d19940db070887bd42906d97
4
+ data.tar.gz: 083aab204d6d7263ffb05934cce8fc7d757f069c
5
5
  SHA512:
6
- metadata.gz: 8aef87866ce7bdea33d3490adb6046e6eeea50fc07b46dd21924bec5e9ead803695eb901637f8c18448cd29d03208f0ca2f8fd47a3735153e08f4cc88e2647de
7
- data.tar.gz: c0e0ed306a8b784234d8edf544133c3ee0382e647236a2f29f154e1284a1e627d0d9f0fafae09b61ff35f34b36aa69435027dea8e886e6465500dab5d8a4ba0e
6
+ metadata.gz: 3548362a217c606c6eab222b9c51ec396354834fed95fba57702ca87293bec36c2e2ab799cfcbd944e80f1fcfdeb1ea405c606a6367c4630e6ac35cec497b80a
7
+ data.tar.gz: 267df7e14dcab41af2da136c2045a86b755441cb8693ad189d9361fad520a95792229bde6dd6ed3473b3fe6ceee117745e9136b253567c83cd23769eddaf2767
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ ## mail_room 0.3.0 ##
2
+
3
+ * Reconnect and idle if disconnected during an existing idle.
4
+ * Set idling thread to abort on exception so any unhandled exceptions will stop mail_room running.
5
+
6
+ *Tony Pitale*
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  mail_room is a configuration based process that will idle on IMAP connections and POST to a delivery URL whenever a new message is received on the configured mailbox and folder.
4
4
 
5
5
  [![Build Status](https://travis-ci.org/tpitale/mail_room.png?branch=master)](https://travis-ci.org/tpitale/mail_room)
6
+ [![Code Climate](https://codeclimate.com/github/tpitale/mail_room.png)](https://codeclimate.com/github/tpitale/mail_room)
6
7
 
7
8
  ## Installation ##
8
9
 
@@ -26,33 +27,35 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
26
27
 
27
28
  ## Configuration ##
28
29
 
29
- ---
30
- :mailboxes:
31
- -
32
- :email: "user1@gmail.com"
33
- :password: "password"
34
- :name: "inbox"
35
- :delivery_url: "http://localhost:3000/inbox"
36
- :delivery_token: "abcdefg"
37
- -
38
- :email: "user2@gmail.com"
39
- :password: "password"
40
- :name: "inbox"
41
- :delivery_method: postback
42
- :delivery_url: "http://localhost:3000/inbox"
43
- :delivery_token: "abcdefg"
44
- -
45
- :email: "user3@gmail.com"
46
- :password: "password"
47
- :name: "inbox"
48
- :delivery_method: logger
49
- :log_path: "/var/log/user3-email.log"
50
- -
51
- :email: "user4@gmail.com"
52
- :password: "password"
53
- :name: "inbox"
54
- :delivery_method: letter_opener
55
- :location: "/tmp/user4-email"
30
+ ```yaml
31
+ ---
32
+ :mailboxes:
33
+ -
34
+ :email: "user1@gmail.com"
35
+ :password: "password"
36
+ :name: "inbox"
37
+ :delivery_url: "http://localhost:3000/inbox"
38
+ :delivery_token: "abcdefg"
39
+ -
40
+ :email: "user2@gmail.com"
41
+ :password: "password"
42
+ :name: "inbox"
43
+ :delivery_method: postback
44
+ :delivery_url: "http://localhost:3000/inbox"
45
+ :delivery_token: "abcdefg"
46
+ -
47
+ :email: "user3@gmail.com"
48
+ :password: "password"
49
+ :name: "inbox"
50
+ :delivery_method: logger
51
+ :log_path: "/var/log/user3-email.log"
52
+ -
53
+ :email: "user4@gmail.com"
54
+ :password: "password"
55
+ :name: "inbox"
56
+ :delivery_method: letter_opener
57
+ :location: "/tmp/user4-email"
58
+ ```
56
59
 
57
60
  ## delivery_method ##
58
61
 
@@ -60,9 +63,7 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
60
63
 
61
64
  Requires `faraday` gem be installed.
62
65
 
63
- *NOTE:* If you're using Ruby `>= 2.0`, you'll need
64
- to use Faraday from `master` for the time being. I will investigate any issues
65
- once version `0.9.0` final is released.
66
+ *NOTE:* If you're using Ruby `>= 2.0`, you'll need to use Faraday from `>= 0.8.9`. Versions before this seem to have some weird behavior with `mail_room`.
66
67
 
67
68
  The default delivery method, requires `delivery_url` and `delivery_token` in
68
69
  configuration.
data/lib/mail_room.rb CHANGED
@@ -2,10 +2,16 @@ require 'net/imap'
2
2
  require 'optparse'
3
3
  require 'yaml'
4
4
 
5
+ # The MailRoom namespace
5
6
  module MailRoom
6
- # def self.logger
7
- # @logger ||= Logger.new(STDOUT)
8
- # end
7
+ # The MailRoom Delivery namespace holds any defined delivery methods
8
+ # including:
9
+ #
10
+ # * postback (default)
11
+ # * letter_opener
12
+ # * logger
13
+ # * noop
14
+ module Delivery; end
9
15
  end
10
16
 
11
17
  require "mail_room/version"
data/lib/mail_room/cli.rb CHANGED
@@ -1,7 +1,13 @@
1
1
  module MailRoom
2
+ # The CLI parses ARGV into configuration to start the coordinator with.
3
+ # @author Tony Pitale
2
4
  class CLI
3
5
  attr_accessor :configuration, :coordinator
4
6
 
7
+ # Initialize a new CLI instance to handle option parsing from arguments
8
+ # into configuration to start the coordinator running on all mailboxes
9
+ #
10
+ # @param args [Array] `ARGV` passed from `bin/mail_room`
5
11
  def initialize(args)
6
12
  options = {}
7
13
 
@@ -29,6 +35,7 @@ module MailRoom
29
35
  self.coordinator = Coordinator.new(configuration.mailboxes)
30
36
  end
31
37
 
38
+ # Start the coordinator running, sets up signal traps
32
39
  def start
33
40
  Signal.trap(:INT) do
34
41
  coordinator.running = false
@@ -1,7 +1,10 @@
1
1
  module MailRoom
2
+ # Wraps configuration for a set of individual mailboxes with global config
3
+ # @author Tony Pitale
2
4
  class Configuration
3
5
  attr_accessor :mailboxes, :daemonize, :log_path, :pid_path
4
6
 
7
+ # Initialize a new configuration of mailboxes
5
8
  def initialize(options={})
6
9
  self.mailboxes = []
7
10
 
@@ -12,6 +15,9 @@ module MailRoom
12
15
  end
13
16
  end
14
17
 
18
+ # Builds individual mailboxes from YAML configuration
19
+ #
20
+ # @param mailboxes_config
15
21
  def set_mailboxes(mailboxes_config)
16
22
  mailboxes_config.each do |attributes|
17
23
  self.mailboxes << Mailbox.new(attributes)
@@ -1,7 +1,11 @@
1
1
  module MailRoom
2
+ # Coordinate the mailbox watchers
3
+ # @author Tony Pitale
2
4
  class Coordinator
3
5
  attr_accessor :watchers, :running
4
6
 
7
+ # build watchers for a set of mailboxes
8
+ # @params mailboxes [Array<MailRoom::Mailbox>] mailboxes to be watched
5
9
  def initialize(mailboxes)
6
10
  self.watchers = []
7
11
 
@@ -10,22 +14,26 @@ module MailRoom
10
14
 
11
15
  alias :running? :running
12
16
 
17
+ # start each of the watchers to running
13
18
  def run
14
19
  watchers.each(&:run)
15
20
 
16
21
  self.running = true
17
22
 
18
23
  sleep_while_running
19
-
24
+ ensure
20
25
  quit
21
26
  end
22
-
23
- def sleep_while_running
24
- while(running?) do; sleep 1; end
25
- end
26
27
 
28
+ # quit each of the watchers when we're done running
27
29
  def quit
28
30
  watchers.each(&:quit)
29
31
  end
32
+
33
+ private
34
+ # @private
35
+ def sleep_while_running
36
+ while(running?) do; sleep 1; end
37
+ end
30
38
  end
31
39
  end
@@ -4,11 +4,17 @@ require 'letter_opener'
4
4
 
5
5
  module MailRoom
6
6
  module Delivery
7
+ # LetterOpener Delivery method
8
+ # @author Tony Pitale
7
9
  class LetterOpener
10
+ # Build a new delivery, hold the mailbox configuration
11
+ # @param [MailRoom::Mailbox]
8
12
  def initialize(mailbox)
9
13
  @mailbox = mailbox
10
14
  end
11
15
 
16
+ # Trigger `LetterOpener` to deliver our message
17
+ # @param message [String] the email message as a string, RFC822 format
12
18
  def deliver(message)
13
19
  method = ::LetterOpener::DeliveryMethod.new(:location => @mailbox.location)
14
20
  method.deliver!(Mail.read_from_string(message))
@@ -2,7 +2,12 @@ require 'logger'
2
2
 
3
3
  module MailRoom
4
4
  module Delivery
5
+ # File/STDOUT Logger Delivery method
6
+ # @author Tony Pitale
5
7
  class Logger
8
+ # Build a new delivery, hold the mailbox configuration
9
+ # open a file or stdout for IO depending on the configuration
10
+ # @param [MailRoom::Mailbox]
6
11
  def initialize(mailbox)
7
12
  io = File.open(mailbox.log_path, 'a') if mailbox.log_path
8
13
  io ||= STDOUT
@@ -12,6 +17,8 @@ module MailRoom
12
17
  @logger = ::Logger.new(io)
13
18
  end
14
19
 
20
+ # Write the message to our logger
21
+ # @param message [String] the email message as a string, RFC822 format
15
22
  def deliver(message)
16
23
  @logger.info message
17
24
  end
@@ -1,9 +1,13 @@
1
1
  module MailRoom
2
2
  module Delivery
3
+ # Noop Delivery method
4
+ # @author Tony Pitale
3
5
  class Noop
6
+ # build a new delivery, do nothing
4
7
  def initialize(*)
5
8
  end
6
9
 
10
+ # accept the delivery, do nothing
7
11
  def deliver(*)
8
12
  end
9
13
  end
@@ -2,11 +2,17 @@ require 'faraday'
2
2
 
3
3
  module MailRoom
4
4
  module Delivery
5
+ # Postback Delivery method
6
+ # @author Tony Pitale
5
7
  class Postback
8
+ # Build a new delivery, hold the mailbox configuration
9
+ # @param [MailRoom::Mailbox]
6
10
  def initialize(mailbox)
7
11
  @mailbox = mailbox
8
12
  end
9
13
 
14
+ # deliver the message using Faraday to the configured mailbox url
15
+ # @param message [String] the email message as a string, RFC822 format
10
16
  def deliver(message)
11
17
  connection = Faraday.new
12
18
  connection.token_auth @mailbox.delivery_token
@@ -1,5 +1,6 @@
1
1
  module MailRoom
2
- Mailbox = Struct.new(*[
2
+ # Mailbox Configuration fields
3
+ FIELDS = [
3
4
  :email,
4
5
  :password,
5
6
  :name,
@@ -8,9 +9,13 @@ module MailRoom
8
9
  :delivery_url, # for postback
9
10
  :delivery_token, # for postback
10
11
  :location # for letter_opener
11
- ])
12
+ ]
12
13
 
13
- class Mailbox
14
+ # Holds configuration for each of the email accounts we wish to monitor
15
+ # and deliver email to when new emails arrive over imap
16
+ Mailbox = Struct.new(*FIELDS) do
17
+ # Store the configuration and require the appropriate delivery method
18
+ # @param attributes [Hash] configuration options
14
19
  def initialize(attributes={})
15
20
  super(*attributes.values_at(*members))
16
21
 
@@ -31,6 +36,8 @@ module MailRoom
31
36
  end
32
37
  end
33
38
 
39
+ # deliver the imap email message
40
+ # @param message [Net::IMAP::FetchData]
34
41
  def deliver(message)
35
42
  delivery_klass.new(self).deliver(message.attr['RFC822'])
36
43
  end
@@ -1,10 +1,16 @@
1
1
  module MailRoom
2
+ # Fetches new email messages for delivery
3
+ # @author Tony Pitale
2
4
  class MailboxHandler
5
+ # build a handler for this mailbox and our imap connection
6
+ # @param mailbox [MailRoom::Mailbox] the mailbox configuration
7
+ # @param imap [Net::IMAP::Connection] the open connection to gmail
3
8
  def initialize(mailbox, imap)
4
9
  @mailbox = mailbox
5
10
  @imap = imap
6
11
  end
7
12
 
13
+ # deliver each of the new messages
8
14
  def process
9
15
  # return if idling? || !running?
10
16
 
@@ -16,17 +22,27 @@ module MailRoom
16
22
  end
17
23
  end
18
24
 
25
+ private
26
+ # @private
27
+ # fetch all messages for the new message ids
19
28
  def new_messages
20
29
  messages_for_ids(new_message_ids)
21
30
  end
22
31
 
23
- # label messages?
24
- # @imap.store(id, "+X-GM-LABELS", [label])
32
+ # TODO: label messages?
33
+ # @imap.store(id, "+X-GM-LABELS", [label])
25
34
 
35
+ # @private
36
+ # search for all new (unseen) message ids
37
+ # @return [Array<Integer>] message ids
26
38
  def new_message_ids
27
39
  @imap.search('UNSEEN')
28
40
  end
29
41
 
42
+ # @private
43
+ # fetch the email for all given ids in RFC822 format
44
+ # @param ids [Array<Integer>] list of message ids
45
+ # @return [Array<Net::IMAP::FetchData>] the net/imap messages for the given ids
30
46
  def messages_for_ids(ids)
31
47
  return [] if ids.empty?
32
48
 
@@ -1,68 +1,105 @@
1
1
  module MailRoom
2
- # split up between processing and idling?
2
+ # TODO: split up between processing and idling?
3
+
4
+ # Watch a Mailbox
5
+ # @author Tony Pitale
3
6
  class MailboxWatcher
4
7
  attr_accessor :idling_thread
5
8
 
9
+ # Watch a new mailbox
10
+ # @param mailbox [MailRoom::Mailbox] the mailbox to watch
6
11
  def initialize(mailbox)
7
12
  @mailbox = mailbox
8
13
 
14
+ reset
9
15
  @running = false
10
- @logged_in = false
11
- @idling = false
12
16
  end
13
17
 
18
+ # build a net/imap connection to google imap
14
19
  def imap
15
20
  @imap ||= Net::IMAP.new('imap.gmail.com', :port => 993, :ssl => true)
16
21
  end
17
22
 
23
+ # build a handler to process mailbox messages
18
24
  def handler
19
25
  @handler ||= MailboxHandler.new(@mailbox, imap)
20
26
  end
21
27
 
28
+ # are we running?
29
+ # @return [Boolean]
22
30
  def running?
23
31
  @running
24
32
  end
25
33
 
34
+ # is the connection logged in?
35
+ # @return [Boolean]
26
36
  def logged_in?
27
37
  @logged_in
28
38
  end
29
39
 
40
+ # is the connection blocked idling?
41
+ # @return [Boolean]
30
42
  def idling?
31
43
  @idling
32
44
  end
33
45
 
46
+ # is the imap connection closed?
47
+ # @return [Boolean]
48
+ def disconnected?
49
+ @imap.disconnected?
50
+ end
51
+
52
+ # is the connection ready to idle?
53
+ # @return [Boolean]
54
+ def ready_to_idle?
55
+ logged_in? && !idling?
56
+ end
57
+
58
+ # is the response for a new message?
59
+ # @param response [Net::IMAP::TaggedResponse] the imap response from idle
60
+ # @return [Boolean]
61
+ def message_exists?(response)
62
+ response.respond_to?(:name) && response.name == 'EXISTS'
63
+ end
64
+
65
+ # log in and set the mailbox
34
66
  def setup
67
+ reset
35
68
  log_in
36
69
  set_mailbox
37
70
  end
38
71
 
72
+ # clear disconnected imap
73
+ # reset imap state
74
+ def reset
75
+ @imap = nil
76
+ @logged_in = false
77
+ @idling = false
78
+ end
79
+
80
+ # send the imap login command to google
39
81
  def log_in
40
82
  imap.login(@mailbox.email, @mailbox.password)
41
83
  @logged_in = true
42
84
  end
43
85
 
86
+ # select the mailbox name we want to use
44
87
  def set_mailbox
45
88
  imap.select(@mailbox.name) if logged_in?
46
89
  end
47
90
 
91
+ # maintain an imap idle connection
48
92
  def idle
49
- return unless logged_in?
93
+ return unless ready_to_idle?
50
94
 
51
95
  @idling = true
52
96
 
53
- imap.idle do |response|
54
- if response.respond_to?(:name) && response.name == 'EXISTS'
55
- imap.idle_done
56
- end
57
- end
58
-
97
+ imap.idle(&idle_handler)
98
+ ensure
59
99
  @idling = false
60
100
  end
61
101
 
62
- def process_mailbox
63
- handler.process
64
- end
65
-
102
+ # trigger the idle to finish and wait for the thread to finish
66
103
  def stop_idling
67
104
  return unless idling?
68
105
 
@@ -70,6 +107,7 @@ module MailRoom
70
107
  idling_thread.join
71
108
  end
72
109
 
110
+ # run the mailbox watcher
73
111
  def run
74
112
  setup
75
113
 
@@ -77,17 +115,38 @@ module MailRoom
77
115
 
78
116
  self.idling_thread = Thread.start do
79
117
  while(running?) do
80
- # block until we stop idling
81
- idle
82
- # when new messages are ready
83
- process_mailbox
118
+ begin
119
+ # block until we stop idling
120
+ idle
121
+
122
+ # when new messages are ready
123
+ process_mailbox
124
+ rescue Net::IMAP::Error => e
125
+ # we've been disconnected, so re-setup
126
+ setup
127
+ end
84
128
  end
85
129
  end
130
+
131
+ idling_thread.abort_on_exception = true
86
132
  end
87
133
 
134
+ # stop running
88
135
  def quit
89
136
  @running = false
90
137
  stop_idling
138
+ # disconnect
139
+ end
140
+
141
+ # trigger the handler to process this mailbox for new messages
142
+ def process_mailbox
143
+ handler.process
144
+ end
145
+
146
+ private
147
+ # @private
148
+ def idle_handler
149
+ lambda {|response| imap.idle_done if message_exists?(response)}
91
150
  end
92
151
  end
93
152
  end
@@ -1,3 +1,4 @@
1
1
  module MailRoom
2
- VERSION = "0.2.0"
2
+ # Current version of MailRoom gem
3
+ VERSION = "0.3.0"
3
4
  end
@@ -34,9 +34,11 @@ describe MailRoom::Coordinator do
34
34
 
35
35
  it 'should go to sleep after running watchers' do
36
36
  coordinator = MailRoom::Coordinator.new([])
37
- coordinator.stubs(:sleep_while_running)
37
+ coordinator.stubs(:running=)
38
+ coordinator.stubs(:running?).returns(false)
38
39
  coordinator.run
39
- coordinator.should have_received(:sleep_while_running)
40
+ coordinator.should have_received(:running=).with(true)
41
+ coordinator.should have_received(:running?)
40
42
  end
41
43
 
42
44
  it 'should set attribute running to true' do
@@ -47,15 +49,6 @@ describe MailRoom::Coordinator do
47
49
  end
48
50
  end
49
51
 
50
- describe '#sleep_while_running' do
51
- it 'should check the running state' do
52
- coordinator = MailRoom::Coordinator.new([])
53
- coordinator.stubs(:running?).returns(false)
54
- coordinator.sleep_while_running
55
- coordinator.should have_received(:running?)
56
- end
57
- end
58
-
59
52
  describe '#quit' do
60
53
  it 'quits each watcher' do
61
54
  watcher = stub(:quit)
@@ -118,7 +118,7 @@ describe MailRoom::MailboxWatcher do
118
118
 
119
119
  describe '#stop_idling' do
120
120
  let(:imap) {stub}
121
- let(:idling_thread) {stub}
121
+ let(:idling_thread) {stub(:abort_on_exception=)}
122
122
  let(:watcher) {MailRoom::MailboxWatcher.new(nil)}
123
123
 
124
124
  before :each do
@@ -160,7 +160,7 @@ describe MailRoom::MailboxWatcher do
160
160
  let(:watcher) {MailRoom::MailboxWatcher.new(nil)}
161
161
 
162
162
  before :each do
163
- Thread.stubs(:start).yields
163
+ Thread.stubs(:start).yields.returns(stub(:abort_on_exception=))
164
164
  watcher.stubs(:setup)
165
165
  end
166
166
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mail_room
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Pitale
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-16 00:00:00.000000000 Z
11
+ date: 2014-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -131,9 +131,9 @@ extensions: []
131
131
  extra_rdoc_files: []
132
132
  files:
133
133
  - .gitignore
134
- - .rspec
135
134
  - .ruby-version
136
135
  - .travis.yml
136
+ - CHANGELOG.md
137
137
  - Gemfile
138
138
  - LICENSE.txt
139
139
  - README.md
@@ -182,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
182
182
  version: '0'
183
183
  requirements: []
184
184
  rubyforge_project:
185
- rubygems_version: 2.0.3
185
+ rubygems_version: 2.0.14
186
186
  signing_key:
187
187
  specification_version: 4
188
188
  summary: mail_room will proxy email (gmail) from IMAP to a callback URL, logger, or
@@ -199,3 +199,4 @@ test_files:
199
199
  - spec/lib/mailbox_spec.rb
200
200
  - spec/lib/mailbox_watcher_spec.rb
201
201
  - spec/spec_helper.rb
202
+ has_rdoc:
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --color
2
- --format nested