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 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
 
@@ -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
@@ -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.3.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-08 00:00:00.000000000 Z
12
+ date: 2013-02-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  type: :runtime