Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ begin
gem.post_install_message = "\n\033[34mIf ruby-gmail saves you TWO hours of work, want to compensate me for, like, a half-hour?\nSupport me in making new and better gems:\033[0m \033[31;4mhttp://pledgie.com/campaigns/7087\033[0m\n\n"
gem.add_dependency('shared-mime-info', '>= 0')
gem.add_dependency('mail', '>= 2.2.1')
gem.add_dependency('mime', '>= 0.1')
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
end
Jeweler::GemcutterTasks.new
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.0
0.2.3
95 changes: 86 additions & 9 deletions lib/gmail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,101 @@ class << self
# gmail.label('News')
#
###########################

def inbox
in_label('inbox')
end

def create_label(name)
@xlist_result = nil
imap.create(name)
end

# List the available labels
def labels
(imap.list("", "%") + imap.list("[Gmail]/", "%")).inject([]) { |labels,label|
label[:name].each_line { |l| labels << l }; labels }
labels = []
prefixes = ['']
done = []
until prefixes.empty?
prefix = prefixes.shift
done << prefix
(imap.list(prefix, "%")||[]).each { |e|
if e[:attr].include?(:Haschildren)
unless done.include?(e[:name]+"/") or e[:name].empty?
prefixes << e[:name]+"/"
end
end
unless e[:attr].include?(:Noselect)
labels << e[:name]
end
}
end
labels
end

def imap_xlist
unless @imap.respond_to?(:xlist)
def @imap.xlist(refname, mailbox)
handler = proc do |resp|
if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "XLIST" && resp.raw_data.nil?
list_resp = Net::IMAP::ResponseParser.new.instance_eval {
@str, @pos, @token = "#{resp.name} " + resp.data, 0, nil
@lex_state = Net::IMAP::ResponseParser::EXPR_BEG
list_response
}
if @responses['XLIST'].last == resp.data
@responses['XLIST'].pop
@responses['XLIST'].push(list_resp.data)
end
end
end
synchronize do
add_response_handler(handler)
send_command('XLIST', '', '*')
remove_response_handler(handler)
return @responses.delete('XLIST')
end
end
end

@xlist_result ||= @imap.xlist('', '*')
end

def self.gmail_label_types
[:Inbox, :Allmail, :Spam, :Trash, :Drafts, :Important, :Starred, :Sent]
end

def gmail_label_types
self.class.gmail_label_types
end

def normal_labels
imap_xlist.reject { |label|
label.attr.include?(:Noselect) or label.attr.any? { |flag| gmail_label_types.include?(flag) }
}.map { |label|
label.name
}
end

def imap_xlist!
@xlist_result = nil
imap_xlist
end

def label_of_type(type)
info = imap_xlist.find { |l| l.attr.include?(type) }
info && info.name || nil
end

gmail_label_types.each do |label|
module_eval <<-EOL
def #{label.to_s.downcase} &block
in_label(#{label.to_s.downcase}_label, &block)
end
def #{label.to_s.downcase}_label
label_of_type(#{label.inspect})
end
EOL
end

# gmail.label(name)
def label(name)
mailboxes[name] ||= Mailbox.new(self, mailbox)
mailboxes[name] ||= Mailbox.new(self, name)
end
alias :mailbox :label

Expand Down Expand Up @@ -119,7 +196,7 @@ def in_mailbox(mailbox, &block)
end
return value
else
mailboxes[name] ||= Mailbox.new(self, mailbox)
mailboxes[mailbox] ||= Mailbox.new(self, mailbox)
end
end
alias :in_label :in_mailbox
Expand Down
155 changes: 148 additions & 7 deletions lib/gmail/mailbox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,39 @@ def to_s
name
end

FLAG_KEYWORDS = [
['ANSWERED', 'UNANSWERED'],
['DELETED', 'UNDELETED'],
['DRAFT', 'UNDRAFT'],
['FLAGGED', 'UNFLAGGED'],
['RECENT', 'OLD'],
['SEEN', 'UNSEEN']
]
VALID_SEARCH_KEYS = %w[
ALL
BCC
BEFORE
BODY
CC
FROM
HEADER
KEYWORD
LARGER
NEW
NOT
ON
OR
SENTBEFORE
SENTON
SENTSINCE
SINCE
SMALLER
SUBJECT
TEXT
TO
UID
UNKEYWORD
] + FLAG_KEYWORDS.flatten
# Method: emails
# Args: [ :all | :unread | :read ]
# Opts: {:since => Date.new}
Expand All @@ -42,22 +75,130 @@ def emails(key_or_opts = :all, opts={})
else
raise ArgumentError, "Couldn't make sense of arguments to #emails - should be an optional hash of options preceded by an optional read-status bit; OR simply an array of parameters to pass directly to the IMAP uid_search call."
end

fetch = opts.delete(:fetch)

if !opts.empty?
# Support for several search macros
# :before => Date, :on => Date, :since => Date, :from => String, :to => String
search.concat ['SINCE', opts[:after].to_imap_date] if opts[:after]
search.concat ['BEFORE', opts[:before].to_imap_date] if opts[:before]
search.concat ['ON', opts[:on].to_imap_date] if opts[:on]
search.concat ['FROM', opts[:from]] if opts[:from]
search.concat ['TO', opts[:to]] if opts[:to]
opts = opts.dup
VALID_SEARCH_KEYS.each do |keyword|
key = keyword.downcase.intern
if opts[key]
val = opts.delete(key)
case val
when Date, Time
search.concat([keyword, val.to_imap_date])
when String
search.concat([keyword, val])
when Numeric
search.concat([keyword, val.to_s])
when Array
search.concat([keyword, *val])
when TrueClass, FalseClass
# If it's a known flag keyword & val == false,
# try to invert it's meaning.
if row = FLAG_KEYWORDS.find { |row| row.include?(keyword) }
row_index = row.index(keyword)
altkey = row[ val ? row_index : 1 - row_index ]
search.push(altkey)
else
search.push(keyword) if val
end
when NilClass
next
else
search.push(keyword) # e.g. flag
end
end
end

# API compatibility
search.concat ['SINCE', opts.delete(:after).to_imap_date] if opts[:after]

unless opts.empty?
raise "Unrecognised keys: #{opts.keys.inspect}"
end
end

# puts "Gathering #{(aliases[key] || key).inspect} messages for mailbox '#{name}'..."
list = []

@gmail.in_mailbox(self) do
@gmail.imap.uid_search(search).collect { |uid| messages[uid] ||= Message.new(@gmail, self, uid) }
uids = @gmail.imap.uid_search(search)
list = uids.collect { |uid| messages[uid] ||= Message.new(@gmail, self, uid) }

if fetch
missing = list.reject { |message| message.loaded? }.map { |message| message.uid }
@gmail.imap.uid_fetch(missing, ['ENVELOPE', 'RFC822']).each do |info|
message = messages[info.attr['UID']]
message.envelope = info.attr['ENVELOPE']
message.set_body(info.attr['RFC822'])
end
else
missing = list.reject { |message| message.message_id? }.map { |message| message.uid }
@gmail.imap.uid_fetch(missing, ['ENVELOPE']).each do |info|
message = messages[info.attr['UID']]
message.envelope = info.attr['ENVELOPE']
message.message_id = info.attr['ENVELOPE'].message_id
end
end
end

MessageList.new(@gmail, list)
end

class MessageList
include Enumerable
attr_reader :list
def initialize(gmail, list)
@gmail = gmail
@list = list
end
def size
@list.size
end
def each(&block)
@list.each(&block)
end
def with_label(label)
label = label.is_a?(String) ? @gmail.label(label) : label
@gmail.in_label(label) do |mbox|

# Search for message ids in named folder
search = []
@list.each_with_index do |m, index|
search.unshift "OR" unless index.zero?#.empty?
search << "HEADER" << "Message-ID" << m.message_id
end
uids = @gmail.imap.uid_search(search)

# Fetch envelopes for uids
message_ids = []

missing_uids = uids.collect { |uid|
mbox.messages[uid] ||= Message.new(@gmail, mbox, uid)
}.reject { |message|
if message.loaded? or message.message_id?
message_ids << message.message_id
true
else
false
end
}.map { |message|
message.uid
}

message_ids += @gmail.imap.uid_fetch(missing_uids, ['ENVELOPE']).map do |info|
message = mbox.messages[info.attr['UID']]
message.envelope ||= info.attr['ENVELOPE']
info.attr['ENVELOPE'].message_id
end

MessageList.new(@gmail, @list.select { |m| message_ids.include?(m.message_id) })
end
end
end

# This is a convenience method that really probably shouldn't need to exist, but it does make code more readable
# if seriously all you want is the count of messages.
def count(*args)
Expand Down
Loading