gmail 0.6.0 → 0.7.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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +27 -27
  4. data/.rspec +1 -1
  5. data/.rubocop.yml +53 -13
  6. data/.rubocop_todo.yml +299 -239
  7. data/.travis.yml +19 -19
  8. data/CHANGELOG.md +152 -145
  9. data/Gemfile +5 -3
  10. data/LICENSE +21 -21
  11. data/README.md +380 -355
  12. data/Rakefile +44 -46
  13. data/gmail.gemspec +32 -34
  14. data/lib/gmail.rb +72 -78
  15. data/lib/gmail/client.rb +35 -34
  16. data/lib/gmail/client/base.rb +244 -229
  17. data/lib/gmail/client/plain.rb +24 -24
  18. data/lib/gmail/client/xoauth.rb +67 -68
  19. data/lib/gmail/client/xoauth2.rb +39 -39
  20. data/lib/gmail/imap_extensions.rb +156 -159
  21. data/lib/gmail/labels.rb +80 -79
  22. data/lib/gmail/mailbox.rb +178 -175
  23. data/lib/gmail/message.rb +212 -207
  24. data/lib/gmail/version.rb +3 -3
  25. data/spec/account.yml.example +1 -1
  26. data/spec/account.yml.obfus +2 -2
  27. data/spec/gmail/client/base_spec.rb +5 -5
  28. data/spec/gmail/client/plain_spec.rb +169 -169
  29. data/spec/gmail/client/xoauth2_spec.rb +186 -186
  30. data/spec/gmail/client/xoauth_spec.rb +5 -5
  31. data/spec/gmail/client_spec.rb +5 -5
  32. data/spec/gmail/imap_extensions_spec.rb +47 -47
  33. data/spec/gmail/labels_spec.rb +27 -27
  34. data/spec/gmail/mailbox_spec.rb +84 -84
  35. data/spec/gmail/message_spec.rb +181 -181
  36. data/spec/gmail_spec.rb +40 -39
  37. data/spec/recordings/gmail/_new_connects_with_client_and_give_it_context_when_block_given.yml +61 -28
  38. data/spec/recordings/gmail/_new_connects_with_gmail_service_and_return_valid_connection_object.yml +43 -28
  39. data/spec/recordings/gmail/_new_does_not_raise_error_when_couldn_t_connect_with_given_account.yml +21 -13
  40. data/spec/recordings/gmail/_new_raises_error_when_couldn_t_connect_with_given_account.yml +21 -13
  41. data/spec/recordings/gmail_client_plain/instance/delivers_inline_composed_email.yml +61 -42
  42. data/spec/recordings/gmail_client_plain/instance/does_not_log_in_when_given_gmail_account_is_invalid.yml +21 -13
  43. data/spec/recordings/gmail_client_plain/instance/does_not_raise_error_even_though_gmail_account_is_invalid.yml +21 -13
  44. data/spec/recordings/gmail_client_plain/instance/labels/checks_if_there_is_given_label_defined.yml +409 -196
  45. data/spec/recordings/gmail_client_plain/instance/labels/creates_given_label.yml +280 -151
  46. data/spec/recordings/gmail_client_plain/instance/labels/removes_existing_label.yml +271 -146
  47. data/spec/recordings/gmail_client_plain/instance/labels/returns_list_of_all_available_labels.yml +227 -113
  48. data/spec/recordings/gmail_client_plain/instance/properly_logs_in_to_valid_gmail_account.yml +61 -42
  49. data/spec/recordings/gmail_client_plain/instance/properly_logs_out_from_gmail.yml +61 -42
  50. data/spec/recordings/gmail_client_plain/instance/properly_switches_to_given_mailbox.yml +164 -109
  51. data/spec/recordings/gmail_client_plain/instance/properly_switches_to_given_mailbox_using_block_style.yml +164 -109
  52. data/spec/recordings/gmail_client_plain/instance/raises_error_when_given_gmail_account_is_invalid_and_errors_enabled.yml +21 -13
  53. data/spec/recordings/gmail_client_xo_auth2/instance/does_not_log_in_when_given_gmail_account_is_invalid.yml +24 -13
  54. data/spec/recordings/gmail_client_xo_auth2/instance/labels/checks_if_there_is_given_label_defined.yml +39 -27
  55. data/spec/recordings/gmail_client_xo_auth2/instance/labels/creates_given_label.yml +52 -39
  56. data/spec/recordings/gmail_client_xo_auth2/instance/labels/removes_existing_label.yml +52 -39
  57. data/spec/recordings/gmail_client_xo_auth2/instance/labels/returns_list_of_all_available_labels.yml +39 -27
  58. data/spec/recordings/gmail_client_xo_auth2/instance/properly_logs_in_to_valid_gmail_account.yml +26 -15
  59. data/spec/recordings/gmail_client_xo_auth2/instance/properly_logs_out_from_gmail.yml +26 -15
  60. data/spec/recordings/gmail_client_xo_auth2/instance/properly_switches_to_given_mailbox.yml +63 -40
  61. data/spec/recordings/gmail_client_xo_auth2/instance/properly_switches_to_given_mailbox_using_block_style.yml +63 -40
  62. data/spec/recordings/gmail_client_xo_auth2/instance/raises_error_when_given_gmail_account_is_invalid_and_errors_enabled.yml +24 -13
  63. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/all/localizes_into_the_appropriate_label.yml +229 -116
  64. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/and_the_mailbox_does_not_exist/returns_the_mailbox_name_as_a_string.yml +229 -110
  65. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/drafts/localizes_into_the_appropriate_label.yml +229 -116
  66. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/flagged/localizes_into_the_appropriate_label.yml +229 -116
  67. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/important/localizes_into_the_appropriate_label.yml +229 -116
  68. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/inbox/localizes_into_the_appropriate_label.yml +61 -42
  69. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/junk/localizes_into_the_appropriate_label.yml +229 -116
  70. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/sent/localizes_into_the_appropriate_label.yml +229 -116
  71. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/trash/localizes_into_the_appropriate_label.yml +229 -116
  72. data/spec/recordings/gmail_mailbox/instance/counts_all_emails.yml +595 -277
  73. data/spec/recordings/gmail_mailbox/instance/finds_messages.yml +1049 -586
  74. data/spec/recordings/gmail_mailbox/instance/waits_once.yml +191 -136
  75. data/spec/recordings/gmail_mailbox/instance/waits_repeatedly.yml +217 -141
  76. data/spec/recordings/gmail_mailbox/instance/waits_with_29-minute_re-issue.yml +188 -136
  77. data/spec/recordings/gmail_mailbox/instance/waits_with_an_unblocked_connection.yml +644 -207
  78. data/spec/recordings/gmail_mailbox/on_initialize/sets_client_and_name.yml +61 -42
  79. data/spec/recordings/gmail_mailbox/on_initialize/works_in_inbox_by_default.yml +61 -42
  80. data/spec/recordings/gmail_message/initialize/sets_prefetch_attrs.yml +1068 -578
  81. data/spec/recordings/gmail_message/initialize/sets_uid_and_mailbox.yml +1068 -580
  82. data/spec/recordings/gmail_message/instance_methods/deletes_itself.yml +1084 -637
  83. data/spec/recordings/gmail_message/instance_methods/marks_itself_read.yml +1137 -682
  84. data/spec/recordings/gmail_message/instance_methods/marks_itself_unread.yml +1153 -686
  85. data/spec/recordings/gmail_message/instance_methods/moves_from_one_tag_to_other.yml +1447 -862
  86. data/spec/recordings/gmail_message/instance_methods/removes_a_given_label.yml +1288 -776
  87. data/spec/recordings/gmail_message/instance_methods/removes_a_given_label_with_old_method.yml +1288 -776
  88. data/spec/recordings/gmail_message/instance_methods/sets_given_label.yml +1165 -690
  89. data/spec/recordings/gmail_message/instance_methods/sets_given_label_with_old_method.yml +1157 -691
  90. data/spec/spec_helper.rb +56 -53
  91. data/spec/support/imap_mock.rb +181 -181
  92. data/spec/support/obfuscation.rb +49 -52
  93. metadata +21 -90
  94. data/spec/recordings/gmail_client_plain/instance/_connection_automatically_logs_in_to_gmail_account_when_it_s_called.yml +0 -42
@@ -1,79 +1,80 @@
1
- module Gmail
2
- class Labels
3
- include Enumerable
4
- attr_reader :connection
5
- alias :conn :connection
6
-
7
- def initialize(connection)
8
- @connection = connection
9
- end
10
-
11
- # Get list of all defined labels.
12
- def all
13
- @list = []
14
-
15
- ## check each item in list for subfolders
16
- conn.list("", "%").each { |l| sublabels_or_label(l) }
17
-
18
- @list.inject([]) do |labels, label|
19
- label[:name].each_line { |l| labels << Net::IMAP.decode_utf7(l) }
20
- labels
21
- end
22
- end
23
- alias :list :all
24
- alias :to_a :all
25
-
26
- def sublabels_or_label(label)
27
- if label.attr.include? :Hasnochildren
28
- @list << label
29
- else
30
- @list << label
31
- conn.list("#{label.name}/", "%").each { |l| sublabels_or_label(l) }
32
- end
33
- end
34
-
35
- def each(*args, &block)
36
- all.each(*args, &block)
37
- end
38
-
39
- # Returns +true+ when given label defined.
40
- def exists?(label)
41
- all.include?(label)
42
- end
43
- alias :exist? :exists?
44
-
45
- # Creates given label in your account.
46
- def create(label)
47
- !!conn.create(Net::IMAP.encode_utf7(label)) rescue false
48
- end
49
- alias :new :create
50
- alias :add :create
51
-
52
- # Deletes given label from your account.
53
- def delete(label)
54
- !!conn.delete(Net::IMAP.encode_utf7(label)) rescue false
55
- end
56
- alias :remove :delete
57
-
58
- def inspect
59
- "#<Gmail::Labels#{'0x%04x' % (object_id << 1)}>"
60
- end
61
-
62
- # Localizes a specific label flag into a label name
63
-
64
- # Accepts standard mailbox flags returned by LIST's special-use extension:
65
- # :Inbox, :All, :Drafts, :Sent, :Trash, :Important, :Junk, :Flagged
66
- # and their string equivalents. Capitalization agnostic.
67
- def localize(label)
68
- type = label.to_sym.capitalize
69
- if [:All, :Drafts, :Sent, :Trash, :Important, :Junk, :Flagged].include? type
70
- @mailboxes ||= connection.list("", "*")
71
- @mailboxes.select { |box| box.attr.include? type }.collect(&:name).compact.uniq.first || label.to_s
72
- elsif type == :Inbox
73
- 'INBOX'
74
- else
75
- label
76
- end
77
- end
78
- end # Labels
79
- end # Gmail
1
+ module Gmail
2
+ class Labels
3
+ include Enumerable
4
+ attr_reader :connection
5
+ alias :conn :connection
6
+
7
+ def initialize(connection)
8
+ @connection = connection
9
+ end
10
+
11
+ # Get list of all defined labels.
12
+ def all
13
+ @list = []
14
+
15
+ ## check each item in list for subfolders
16
+ conn.list("", "%").each { |l| sublabels_or_label(l) }
17
+
18
+ @list.inject([]) do |labels, label|
19
+ label[:name].each_line { |l| labels << Net::IMAP.decode_utf7(l) }
20
+ labels
21
+ end
22
+ end
23
+ alias :list :all
24
+ alias :to_a :all
25
+
26
+ def sublabels_or_label(label)
27
+ @list << label
28
+ return if label.attr.include? :Hasnochildren
29
+ conn.list("#{label.name}/", "%").each { |l| sublabels_or_label(l) }
30
+ end
31
+
32
+ def each(*args, &block)
33
+ all.each(*args, &block)
34
+ end
35
+
36
+ # Returns +true+ when given label defined.
37
+ def exists?(label)
38
+ all.include?(label)
39
+ end
40
+ alias :exist? :exists?
41
+
42
+ # Creates given label in your account.
43
+ def create(label)
44
+ !!conn.create(Net::IMAP.encode_utf7(label))
45
+ rescue StandardError
46
+ false
47
+ end
48
+ alias :new :create
49
+ alias :add :create
50
+
51
+ # Deletes given label from your account.
52
+ def delete(label)
53
+ !!conn.delete(Net::IMAP.encode_utf7(label))
54
+ rescue StandardError
55
+ false
56
+ end
57
+ alias :remove :delete
58
+
59
+ def inspect
60
+ "#<Gmail::Labels#{'0x%04x' % (object_id << 1)}>"
61
+ end
62
+
63
+ # Localizes a specific label flag into a label name
64
+
65
+ # Accepts standard mailbox flags returned by LIST's special-use extension:
66
+ # :Inbox, :All, :Drafts, :Sent, :Trash, :Important, :Junk, :Flagged
67
+ # and their string equivalents. Capitalization agnostic.
68
+ def localize(label)
69
+ type = label.to_sym.capitalize
70
+ if [:All, :Drafts, :Sent, :Trash, :Important, :Junk, :Flagged].include? type
71
+ @mailboxes ||= connection.list("", "*")
72
+ @mailboxes.select { |box| box.attr.include? type }.collect(&:name).compact.uniq.first || label.to_s
73
+ elsif type == :Inbox
74
+ 'INBOX'
75
+ else
76
+ label
77
+ end
78
+ end
79
+ end # Labels
80
+ end # Gmail
@@ -1,175 +1,178 @@
1
- module Gmail
2
- class Mailbox
3
- MAILBOX_ALIASES = {
4
- :all => ['ALL'],
5
- :seen => ['SEEN'],
6
- :unseen => ['UNSEEN'],
7
- :read => ['SEEN'],
8
- :unread => ['UNSEEN'],
9
- :flagged => ['FLAGGED'],
10
- :unflagged => ['UNFLAGGED'],
11
- :starred => ['FLAGGED'],
12
- :unstarred => ['UNFLAGGED'],
13
- :deleted => ['DELETED'],
14
- :undeleted => ['UNDELETED'],
15
- :draft => ['DRAFT'],
16
- :undrafted => ['UNDRAFT']
17
- }
18
-
19
- attr_reader :name
20
- attr_reader :encoded_name
21
-
22
- def initialize(gmail, name = "INBOX")
23
- @name = Net::IMAP.decode_utf7(name)
24
- @encoded_name = Net::IMAP.encode_utf7(name)
25
- @gmail = gmail
26
- end
27
-
28
- def fetch_uids(*args)
29
- args << :all if args.size == 0
30
-
31
- if args.first.is_a?(Symbol)
32
- search = MAILBOX_ALIASES[args.shift].dup
33
- opts = args.first.is_a?(Hash) ? args.first : {}
34
-
35
- opts[:after] and search.concat ['SINCE', opts[:after].to_imap_date]
36
- opts[:before] and search.concat ['BEFORE', opts[:before].to_imap_date]
37
- opts[:on] and search.concat ['ON', opts[:on].to_imap_date]
38
- opts[:from] and search.concat ['FROM', opts[:from]]
39
- opts[:to] and search.concat ['TO', opts[:to]]
40
- opts[:subject] and search.concat ['SUBJECT', opts[:subject]]
41
- opts[:label] and search.concat ['LABEL', opts[:label]]
42
- opts[:attachment] and search.concat ['HAS', 'attachment']
43
- opts[:search] and search.concat ['BODY', opts[:search]]
44
- opts[:body] and search.concat ['BODY', opts[:body]]
45
- opts[:uid] and search.concat ['UID', opts[:uid]]
46
- opts[:gm] and search.concat ['X-GM-RAW', opts[:gm]]
47
- opts[:query] and search.concat opts[:query]
48
-
49
- @gmail.mailbox(name) {
50
- @gmail.conn.uid_search(search)
51
- }
52
- elsif args.first.is_a?(Hash)
53
- fetch_uids(:all, args.first)
54
- else
55
- raise ArgumentError, "Invalid search criteria"
56
- end
57
- end
58
-
59
- # Returns list of emails which meets given criteria.
60
- #
61
- # ==== Examples
62
- #
63
- # gmail.inbox.emails(:all)
64
- # gmail.inbox.emails(:unread, :from => "friend@gmail.com")
65
- # gmail.inbox.emails(:all, :after => Time.now-(20*24*3600))
66
- # gmail.mailbox("Test").emails(:read)
67
- #
68
- # gmail.mailbox("Test") do |box|
69
- # box.emails(:read)
70
- # box.emails(:unread) do |email|
71
- # ... do something with each email...
72
- # end
73
- # end
74
- def emails(*args, &block)
75
- fetch_uids(*args).collect do |uid|
76
- message = Message.new(self, uid)
77
- yield(message) if block_given?
78
- message
79
- end
80
- end
81
- alias :search :emails
82
- alias :find :emails
83
-
84
- def emails_in_batches(*args, &block)
85
- messages = Array.new
86
-
87
- uids = fetch_uids(*args)
88
- if uids && uids.any?
89
- uids.each_slice(100) do |slice|
90
- @gmail.conn.uid_fetch(slice, Message::PREFETCH_ATTRS).each do |data|
91
- message = Message.new(self, nil, data)
92
- yield(message) if block_given?
93
- messages << message
94
- end
95
- end
96
- end
97
-
98
- messages
99
- end
100
- alias :search_in_batches :emails_in_batches
101
- alias :find_in_batches :emails_in_batches
102
-
103
- # This is a convenience method that really probably shouldn't need to exist,
104
- # but it does make code more readable, if seriously all you want is the count
105
- # of messages.
106
- #
107
- # ==== Examples
108
- #
109
- # gmail.inbox.count(:all)
110
- # gmail.inbox.count(:unread, :from => "friend@gmail.com")
111
- # gmail.mailbox("Test").count(:all, :after => Time.now-(20*24*3600))
112
- def count(*args)
113
- emails(*args).size
114
- end
115
-
116
- # This permanently removes messages which are marked as deleted
117
- def expunge
118
- @gmail.mailbox(name) { @gmail.conn.expunge }
119
- end
120
-
121
- def wait(options = {}, &block)
122
- loop do
123
- wait_once(options, &block)
124
- end
125
- end
126
-
127
- def wait_once(options = {})
128
- options[:idle_timeout] ||= 29 * 60
129
-
130
- response = nil
131
- loop do
132
- complete_cond = @gmail.conn.new_cond
133
- complete_now = false
134
-
135
- @gmail.conn.idle do |resp|
136
- if resp.kind_of?(Net::IMAP::ContinuationRequest) && resp.data.text == 'idling'
137
- Thread.new do
138
- @gmail.conn.synchronize do
139
- complete_cond.wait(options[:idle_timeout]) unless complete_now
140
- @gmail.conn.idle_done
141
- end
142
- end
143
- elsif resp.kind_of?(Net::IMAP::UntaggedResponse) && resp.name == 'EXISTS'
144
- response = resp
145
-
146
- @gmail.conn.synchronize do
147
- complete_now = true
148
- complete_cond.signal
149
- end
150
- end
151
- end
152
-
153
- break if response
154
- end
155
-
156
- yield response if block_given?
157
-
158
- nil
159
- end
160
-
161
- def inspect
162
- "#<Gmail::Mailbox#{'0x%04x' % (object_id << 1)} name=#{name}>"
163
- end
164
-
165
- def to_s
166
- name
167
- end
168
-
169
- MAILBOX_ALIASES.each_key { |mailbox|
170
- define_method(mailbox) do |*args, &block|
171
- emails(mailbox, *args, &block)
172
- end
173
- }
174
- end # Message
175
- end # Gmail
1
+ module Gmail
2
+ class Mailbox
3
+ MAILBOX_ALIASES = {
4
+ :all => ['ALL'],
5
+ :seen => ['SEEN'],
6
+ :unseen => ['UNSEEN'],
7
+ :read => ['SEEN'],
8
+ :unread => ['UNSEEN'],
9
+ :flagged => ['FLAGGED'],
10
+ :unflagged => ['UNFLAGGED'],
11
+ :starred => ['FLAGGED'],
12
+ :unstarred => ['UNFLAGGED'],
13
+ :deleted => ['DELETED'],
14
+ :undeleted => ['UNDELETED'],
15
+ :draft => ['DRAFT'],
16
+ :undrafted => ['UNDRAFT']
17
+ }.freeze
18
+
19
+ attr_reader :name
20
+ attr_reader :encoded_name
21
+
22
+ def initialize(gmail, name = "INBOX")
23
+ @name = Net::IMAP.decode_utf7(name)
24
+ @encoded_name = Net::IMAP.encode_utf7(name)
25
+ @gmail = gmail
26
+ end
27
+
28
+ def fetch_uids(*args)
29
+ args << :all if args.empty?
30
+
31
+ if args.first.is_a?(Symbol)
32
+ search = MAILBOX_ALIASES[args.shift].dup
33
+ opts = args.first.is_a?(Hash) ? args.first : {}
34
+
35
+ opts[:after] and search.concat ['SINCE', Net::IMAP.format_date(opts[:after])]
36
+ opts[:before] and search.concat ['BEFORE', Net::IMAP.format_date(opts[:before])]
37
+ opts[:on] and search.concat ['ON', opts[:on].to_imap_date]
38
+ opts[:from] and search.concat ['FROM', opts[:from]]
39
+ opts[:to] and search.concat ['TO', opts[:to]]
40
+ opts[:subject] and search.concat ['SUBJECT', opts[:subject]]
41
+ opts[:label] and search.concat ['LABEL', opts[:label]]
42
+ opts[:attachment] and search.concat ['HAS', 'attachment']
43
+ opts[:search] and search.concat ['BODY', opts[:search]]
44
+ opts[:body] and search.concat ['BODY', opts[:body]]
45
+ opts[:uid] and search.concat ['UID', opts[:uid]]
46
+ opts[:gm] and search.concat ['X-GM-RAW', opts[:gm]]
47
+ opts[:message_id] and search.concat ['X-GM-MSGID', opts[:message_id].to_s]
48
+ opts[:query] and search.concat opts[:query]
49
+
50
+ @gmail.mailbox(name) do
51
+ @gmail.conn.uid_search(search)
52
+ end
53
+ elsif args.first.is_a?(Hash)
54
+ fetch_uids(:all, args.first)
55
+ else
56
+ raise ArgumentError, "Invalid search criteria"
57
+ end
58
+ end
59
+
60
+ # Returns list of emails which meets given criteria.
61
+ #
62
+ # ==== Examples
63
+ #
64
+ # gmail.inbox.emails(:all)
65
+ # gmail.inbox.emails(:unread, :from => "friend@gmail.com")
66
+ # gmail.inbox.emails(:all, :after => Time.now-(20*24*3600))
67
+ # gmail.mailbox("Test").emails(:read)
68
+ #
69
+ # gmail.mailbox("Test") do |box|
70
+ # box.emails(:read)
71
+ # box.emails(:unread) do |email|
72
+ # ... do something with each email...
73
+ # end
74
+ # end
75
+ def emails(*args, &block)
76
+ fetch_uids(*args).collect do |uid|
77
+ message = Message.new(self, uid)
78
+ yield(message) if block_given?
79
+ message
80
+ end
81
+ end
82
+ alias :search :emails
83
+ alias :find :emails
84
+
85
+ def emails_in_batches(*args, &block)
86
+ return [] unless uids.is_a?(Array) && uids.any?
87
+
88
+ uids.each_slice(100).flat_map do |slice|
89
+ find_for_slice(slice, &block)
90
+ end
91
+ end
92
+
93
+ alias :search_in_batches :emails_in_batches
94
+ alias :find_in_batches :emails_in_batches
95
+
96
+ # This is a convenience method that really probably shouldn't need to exist,
97
+ # but it does make code more readable, if seriously all you want is the count
98
+ # of messages.
99
+ #
100
+ # ==== Examples
101
+ #
102
+ # gmail.inbox.count(:all)
103
+ # gmail.inbox.count(:unread, :from => "friend@gmail.com")
104
+ # gmail.mailbox("Test").count(:all, :after => Time.now-(20*24*3600))
105
+ def count(*args)
106
+ emails(*args).size
107
+ end
108
+
109
+ # This permanently removes messages which are marked as deleted
110
+ def expunge
111
+ @gmail.mailbox(name) { @gmail.conn.expunge }
112
+ end
113
+
114
+ def wait(options = {}, &block)
115
+ loop do
116
+ wait_once(options, &block)
117
+ end
118
+ end
119
+
120
+ def wait_once(options = {})
121
+ options[:idle_timeout] ||= 29 * 60
122
+
123
+ response = nil
124
+ loop do
125
+ complete_cond = @gmail.conn.new_cond
126
+ complete_now = false
127
+
128
+ @gmail.conn.idle do |resp|
129
+ if resp.is_a?(Net::IMAP::ContinuationRequest) && resp.data.text == 'idling'
130
+ Thread.new do
131
+ @gmail.conn.synchronize do
132
+ complete_cond.wait(options[:idle_timeout]) unless complete_now
133
+ @gmail.conn.idle_done
134
+ end
135
+ end
136
+ elsif resp.is_a?(Net::IMAP::UntaggedResponse) && resp.name == 'EXISTS'
137
+ response = resp
138
+
139
+ @gmail.conn.synchronize do
140
+ complete_now = true
141
+ complete_cond.signal
142
+ end
143
+ end
144
+ end
145
+
146
+ break if response
147
+ end
148
+
149
+ yield response if block_given?
150
+
151
+ nil
152
+ end
153
+
154
+ def inspect
155
+ "#<Gmail::Mailbox#{'0x%04x' % (object_id << 1)} name=#{name}>"
156
+ end
157
+
158
+ def to_s
159
+ name
160
+ end
161
+
162
+ MAILBOX_ALIASES.each_key do |mailbox|
163
+ define_method(mailbox) do |*args, &block|
164
+ emails(mailbox, *args, &block)
165
+ end
166
+ end
167
+
168
+ private
169
+
170
+ def find_for_slice(slice, &block)
171
+ @gmail.conn.uid_fetch(slice, Message::PREFETCH_ATTRS).map do |data|
172
+ message = Message.new(self, nil, data)
173
+ yield(message) if block_given?
174
+ message
175
+ end
176
+ end
177
+ end # Message
178
+ end # Gmail