em-imap 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of em-imap might be problematic. Click here for more details.
- data/README.md +39 -16
- data/lib/em-imap/client.rb +73 -0
- data/lib/em-imap/listener.rb +10 -0
- metadata +2 -2
data/README.md
CHANGED
@@ -14,6 +14,7 @@ Before you can communicate with an IMAP server, you must first connect to it. Th
|
|
14
14
|
|
15
15
|
For example, to connect to Gmail's IMAP server, you can use the following snippet:
|
16
16
|
|
17
|
+
```ruby
|
17
18
|
require 'rubygems'
|
18
19
|
require 'em-imap'
|
19
20
|
|
@@ -27,6 +28,7 @@ For example, to connect to Gmail's IMAP server, you can use the following snippe
|
|
27
28
|
EM::stop
|
28
29
|
end
|
29
30
|
end
|
31
|
+
```
|
30
32
|
|
31
33
|
### Authenticating
|
32
34
|
|
@@ -34,6 +36,7 @@ There are two authentication mechanisms in IMAP, `LOGIN` and `AUTHENTICATE`, exp
|
|
34
36
|
|
35
37
|
Extending our previous example to also log in to Gmail:
|
36
38
|
|
39
|
+
```ruby
|
37
40
|
client = EM::IMAP.new('imap.gmail.com', 993, true)
|
38
41
|
client.connect.bind! do
|
39
42
|
client.login("conrad.irwin@gmail.com", ENV["GMAIL_PASSWORD"])
|
@@ -42,6 +45,7 @@ Extending our previous example to also log in to Gmail:
|
|
42
45
|
end.errback do |error|
|
43
46
|
puts "Connecting or logging in failed: #{error}"
|
44
47
|
end
|
48
|
+
```
|
45
49
|
|
46
50
|
The `.authenticate` method is more advanced and uses the same extensible mechanism as [Net::IMAP](http://www.ruby-doc.org/stdlib/libdoc/net/imap/rdoc/classes/Net/IMAP.html). The two mechanisms supported by default are `'LOGIN'` and [`'CRAM-MD5'`](http://www.ietf.org/rfc/rfc2195.txt), other mechanisms are provided by gems like [gmail\_xoauth](https://github.com/nfo/gmail_xoauth).
|
47
51
|
|
@@ -49,6 +53,7 @@ The `.authenticate` method is more advanced and uses the same extensible mechani
|
|
49
53
|
|
50
54
|
Once the authentication has completed successfully, you can perform IMAP commands that don't require a currently selected mailbox. For example to get a list of the names of all Gmail mailboxes (including labels):
|
51
55
|
|
56
|
+
```ruby
|
52
57
|
client = EM::IMAP.new('imap.gmail.com', 993, true)
|
53
58
|
client.connect.bind! do
|
54
59
|
client.login("conrad.irwin@gmail.com", ENV["GMAIL_PASSWORD"])
|
@@ -59,6 +64,7 @@ Once the authentication has completed successfully, you can perform IMAP command
|
|
59
64
|
end.errback do |error|
|
60
65
|
puts "Connecting, logging in or listing failed: #{error}"
|
61
66
|
end
|
67
|
+
```
|
62
68
|
|
63
69
|
The useful commands available to you at this point are `.list`, `.create(mailbox)`, `.delete(mailbox)`, `.rename(old_mailbox, new_mailbox)`, `.status(mailbox)`. `.select(mailbox)` and `.examine(mailbox)` are discussed in the next section, and `.subscribe(mailbox)`, `.unsubscribe(mailbox)`, `.lsub` and `.append(mailbox, message, flags?, date_time)` are unlikely to be useful to you immediately. For a full list of IMAP commands, and detailed considerations, please refer to [RFC3501](http://tools.ietf.org/html/rfc3501).
|
64
70
|
|
@@ -68,6 +74,7 @@ In order to do useful things which actual messages, you need to first select a m
|
|
68
74
|
|
69
75
|
For example to search for all emails relevant to em-imap in Gmail:
|
70
76
|
|
77
|
+
```ruby
|
71
78
|
client = EM::IMAP.new('imap.gmail.com', 993, true)
|
72
79
|
client.connect.bind! do
|
73
80
|
client.login("conrad.irwin@gmail.com", ENV["GMAIL_PASSWORD"])
|
@@ -80,9 +87,11 @@ For example to search for all emails relevant to em-imap in Gmail:
|
|
80
87
|
end.errback do |error|
|
81
88
|
puts "Something failed: #{error}"
|
82
89
|
end
|
90
|
+
```
|
83
91
|
|
84
92
|
Once you have a list of message sequence numbers, as returned by search, you can actually read the emails with `.fetch`:
|
85
93
|
|
94
|
+
```ruby
|
86
95
|
client = EM::IMAP.new('imap.gmail.com', 993, true)
|
87
96
|
client.connect.bind! do
|
88
97
|
client.login("conrad.irwin@gmail.com", ENV["GMAIL_PASSWORD"])
|
@@ -97,6 +106,7 @@ Once you have a list of message sequence numbers, as returned by search, you can
|
|
97
106
|
end.errback do |error|
|
98
107
|
puts "Something failed: #{error}"
|
99
108
|
end
|
109
|
+
```
|
100
110
|
|
101
111
|
The useful commands available to you at this point are `.search(*args)`, `.expunge`, `.fetch(messages, attributes)`, `.store(messages, name, values)` and `.copy(messages, mailbox)`. If you'd like to work with UIDs instead of sequence numbers, there are UID based alternatives: `.uid_search`, `.uid_fetch`, `.uid_store` and `.uid_copy`. The `.close` command and `.check` command are unlikely to be useful to you immediately.
|
102
112
|
|
@@ -106,6 +116,7 @@ IMAP has the notion of untagged responses (aka. unsolicited responses). The idea
|
|
106
116
|
|
107
117
|
For example, we could insert a listener into the above example to find out some interesting numbers:
|
108
118
|
|
119
|
+
```ruby
|
109
120
|
end.bind! do
|
110
121
|
client.select('[Google Mail]/All Mail').listen do |response|
|
111
122
|
case response.name
|
@@ -116,22 +127,7 @@ For example, we could insert a listener into the above example to find out some
|
|
116
127
|
end
|
117
128
|
end
|
118
129
|
end.bind! do
|
119
|
-
|
120
|
-
One IMAP command that exists solely to receive such unsolicited responses is IDLE. The IDLE command blocks the connection so that no other commands can use it, so before you can send further commands you must `stop` the IDLE command:
|
121
|
-
|
122
|
-
idler = client.idle
|
123
|
-
|
124
|
-
idler.listen do |response|
|
125
|
-
if (response.name == "EXISTS" rescue nil)
|
126
|
-
puts "Ooh, new emails!"
|
127
|
-
idler.stop
|
128
|
-
idler.callback do
|
129
|
-
# ... process new emails
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end.errback do |e|
|
133
|
-
puts "Idler recieved an error: #{e}"
|
134
|
-
end
|
130
|
+
```
|
135
131
|
|
136
132
|
### Concurrency
|
137
133
|
|
@@ -139,15 +135,18 @@ IMAP is an explicitly concurrent protocol: clients MAY send commands without wai
|
|
139
135
|
|
140
136
|
If you want to receive server responses at any time, you can call `.add_response_handler(&block)` on the client. This returns a deferrable like the IDLE command, on which you can call `stop` to stop receiving responses (which will cause the deferrable to succeed). You should also listen on the `errback` of this deferrable so that you know when the connection is closed:
|
141
137
|
|
138
|
+
```ruby
|
142
139
|
handler = client.add_response_handler do |response|
|
143
140
|
puts "Server says: #{response}"
|
144
141
|
end.errback do |e|
|
145
142
|
puts "Connection closed?: #{e}"
|
146
143
|
end
|
147
144
|
EM::Timer.new(600){ handler.stop }
|
145
|
+
```
|
148
146
|
|
149
147
|
If you want to send commands without waiting for previous replies, you can also do so. em-imap handles the few cases where this is not permitted (for example, during an IDLE command) by queueing the command until the connection becomes available again. If you do this, bear in mind that any blocks that are listening on the connection may receive responses from multiple commands interleaved.
|
150
148
|
|
149
|
+
```ruby
|
151
150
|
client = EM::Imap.new('imap.gmail.com', 993, true)
|
152
151
|
client.connect.callback do
|
153
152
|
logger_in = client.login('conrad.irwin@gmail.com', ENV["GMAIL_PASSWORD"])
|
@@ -160,6 +159,30 @@ If you want to send commands without waiting for previous replies, you can also
|
|
160
159
|
selecter.errback{ |e| searcher.fail e }
|
161
160
|
searcher.errback{ |e| "Something failed: #{e}" }
|
162
161
|
end
|
162
|
+
```
|
163
|
+
|
164
|
+
### IDLE
|
165
|
+
|
166
|
+
IMAP has an IDLE command (aka push-email) that lets the server notify the client when there are new emails to be read. This command is exposed at a low-level, but it's quite hard to use directly. Instead you can simply ask the client to `wait_for_new_emails(&block)`. This takes care of re-issuing the IDLE command every 29 minutes, in addition to ensuring that the connection isn't IDLEing while you're trying to process the results.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
client = EM::IMAP.new('imap.gmail.com', 993, true)
|
170
|
+
client.connect.bind! do
|
171
|
+
client.login("conrad.irwin@gmail.com", ENV["GMAIL_PASSWORD"])
|
172
|
+
end.bind! do
|
173
|
+
client.select('INBOX')
|
174
|
+
end.bind! do
|
175
|
+
|
176
|
+
client.wait_for_new_emails do |response|
|
177
|
+
client.fetch(response.data).callback{ |fetched| puts fetched.inspect }
|
178
|
+
end
|
179
|
+
|
180
|
+
end.errback do |error|
|
181
|
+
puts "Something failed: #{error}"
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
The block you pass to `wait_for_new_emails` should return a deferrable. If that deferrable succeeds then the IDLE loop will continue, if that deferrable fails then the IDLE loop will also fail. If you don't return a deferrable, it will be assumed that you didn't want to handle the incoming email, and IDLEing will be immediately resumed.
|
163
186
|
|
164
187
|
## TODO
|
165
188
|
|
data/lib/em-imap/client.rb
CHANGED
@@ -375,6 +375,79 @@ module EventMachine
|
|
375
375
|
end
|
376
376
|
end
|
377
377
|
|
378
|
+
# A Wrapper around the IDLE command that lets you wait until one email is received
|
379
|
+
#
|
380
|
+
# Returns a deferrable that succeeds when the IDLE command succeeds, or fails when
|
381
|
+
# the IDLE command fails.
|
382
|
+
#
|
383
|
+
# If a new email has arrived, the deferrable will succeed with the EXISTS response,
|
384
|
+
# otherwise it will succeed with nil.
|
385
|
+
#
|
386
|
+
# client.wait_for_one_email.bind! do |response|
|
387
|
+
# process_new_email(response) if response
|
388
|
+
# end
|
389
|
+
#
|
390
|
+
# This method will be default wait for 29minutes as suggested by the IMAP spec.
|
391
|
+
#
|
392
|
+
# WARNING: just as with IDLE, no further commands can be sent over this connection
|
393
|
+
# until this deferrable has succeeded. You can stop it ahead of time if needed by
|
394
|
+
# calling stop on the returned deferrable.
|
395
|
+
#
|
396
|
+
# idler = client.wait_for_one_email.bind! do |response|
|
397
|
+
# process_new_email(response) if response
|
398
|
+
# end
|
399
|
+
# idler.stop
|
400
|
+
#
|
401
|
+
# See also {wait_for_new_emails}
|
402
|
+
#
|
403
|
+
def wait_for_one_email(timeout=29 * 60)
|
404
|
+
exists_response = nil
|
405
|
+
idler = idle
|
406
|
+
EM::Timer.new(timeout) { idler.stop }
|
407
|
+
idler.listen do |response|
|
408
|
+
if Net::IMAP::UntaggedResponse === response && response.name =~ /\AEXISTS\z/i
|
409
|
+
exists_response = response
|
410
|
+
idler.stop
|
411
|
+
end
|
412
|
+
end.transform{ exists_response }
|
413
|
+
end
|
414
|
+
|
415
|
+
# Wait for new emails to arrive, and call the block when they do.
|
416
|
+
#
|
417
|
+
# This method will run until the upstream connection is closed,
|
418
|
+
# re-idling after every 29 minutes as implied by the IMAP spec.
|
419
|
+
# If you want to stop it, call .stop on the returned listener
|
420
|
+
#
|
421
|
+
# idler = client.wait_for_new_emails do |exists_response, &stop_waiting|
|
422
|
+
# client.fetch(exists_response.data).bind! do |response|
|
423
|
+
# puts response
|
424
|
+
# end
|
425
|
+
# end
|
426
|
+
#
|
427
|
+
# idler.stop
|
428
|
+
#
|
429
|
+
# NOTE: the block should return a deferrable that succeeds when you
|
430
|
+
# are done processing the exists_response. At that point, the idler
|
431
|
+
# will be turned back on again.
|
432
|
+
#
|
433
|
+
def wait_for_new_emails(wrapper=Listener.new, &block)
|
434
|
+
wait_for_one_email.listen do |response|
|
435
|
+
wrapper.receive_event response
|
436
|
+
end.bind! do |response|
|
437
|
+
block.call response if response
|
438
|
+
end.bind! do
|
439
|
+
if wrapper.stopped?
|
440
|
+
wrapper.succeed
|
441
|
+
else
|
442
|
+
wait_for_new_emails(wrapper, &block)
|
443
|
+
end
|
444
|
+
end.errback do |*e|
|
445
|
+
wrapper.fail *e
|
446
|
+
end
|
447
|
+
|
448
|
+
wrapper
|
449
|
+
end
|
450
|
+
|
378
451
|
def add_response_handler(&block)
|
379
452
|
@connection.add_response_handler(&block)
|
380
453
|
end
|
data/lib/em-imap/listener.rb
CHANGED
@@ -149,8 +149,18 @@ module EventMachine
|
|
149
149
|
class Listener
|
150
150
|
include ListeningDeferrable
|
151
151
|
def initialize(&block)
|
152
|
+
@stopped = false
|
152
153
|
listen &block if block_given?
|
153
154
|
end
|
155
|
+
|
156
|
+
def stop(*)
|
157
|
+
@stopped = true
|
158
|
+
super
|
159
|
+
end
|
160
|
+
|
161
|
+
def stopped?
|
162
|
+
@stopped
|
163
|
+
end
|
154
164
|
end
|
155
165
|
end
|
156
166
|
end
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: em-imap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.4.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Conrad Irwin
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
type: :runtime
|