mail_room 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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