Parent

Vmail::ImapClient

Attributes

max_seqno[RW]

Public Class Methods

daemon(config) click to toggle source
# File lib/vmail/imap_client.rb, line 515
def self.daemon(config)
  puts "Starting Vmail::ImapClient in dir #{Dir.pwd}"
  $gmail = self.start(config)
  use_uri = config['drb_uri'] || nil # redundant but explicit
  DRb.start_service(use_uri, $gmail)
  uri = DRb.uri
  puts "Starting gmail service at #{uri}"
  uri
end
new(config) click to toggle source
# File lib/vmail/imap_client.rb, line 29
def initialize(config)
  @username, @password = config['username'], config['password']
  #load user-specified value for from field
  @from = config['from'] || config['username']
  @name = config['name']
  @signature = config['signature'] 
  @signature_script = config['signature_script'] 
  @always_cc = config['always_cc']
  @always_bcc = config['always_bcc']
  @mailbox = nil
  @logger = Logger.new(config['logfile'] || STDERR)
  @logger.level = Logger::DEBUG
  $logger = @logger
  @imap_server = config['server'] || 'imap.gmail.com'
  @imap_port = config['port'] || 993
  # generic smtp settings
  @smtp_server = config['smtp_server'] || 'smtp.gmail.com'
  @smtp_port = config['smtp_port'] || 587
  @smtp_domain = config['smtp_domain'] || 'gmail.com'
  @authentication = config['authentication'] || 'plain'
  @width = 100

  @date_formatter_this_year = config['date_format'] || '%b %d %I:%M%P' 
  @date_formatter_prev_years = config['date_format_previous_years'] || '%b %d %Y'
  @date_width = DateTime.parse("12/12/2012 12:12:12").strftime(@date_formatter_this_year).length
  current_message = nil
end
start(config) click to toggle source
# File lib/vmail/imap_client.rb, line 509
def self.start(config)
  imap_client  = self.new config
  imap_client.open
  imap_client
end

Public Instance Methods

append_to_file(message_ids, file) click to toggle source
# File lib/vmail/imap_client.rb, line 283
def append_to_file(message_ids, file)
  message_ids = message_ids.split(',')
  log "Append to file uid set #{message_ids.inspect} to file: #{file}"
  message_ids.each do |message_id|
    message = show_message(message_id)
    File.open(file, 'a') {|f| f.puts(divider('=') + "\n" + message + "\n\n")}
    subject = (message[/^subject:(.*)/,1] || '').strip
    log "Appended message '#{subject}'"
  end
  "Printed #{message_ids.size} message#{message_ids.size == 1 ? '' : 's'} to #{file.strip}"
end
check_for_new_messages() click to toggle source
# File lib/vmail/imap_client.rb, line 206
def check_for_new_messages
  log "Checking for new messages"
  if search_query?
    log "Update aborted because query is search query: #{@query.inspect}"
    return ""
  end
  old_num_messages = @num_messages
  # we need to re-select the mailbox to get the new highest id
  reload_mailbox
  update_query = @query.dup
  # set a new range filter
  # this may generate a negative rane, e.g., "19893:19992" but that seems harmless
  update_query[0] = "#{old_num_messages}:#{@num_messages}"
  ids = reconnect_if_necessary { 
    log "Search #update_query"
    @imap.search(Vmail::Query.args2string(update_query))
  }
  log "- got seqnos: #{ids.inspect}"
  log "- getting seqnos > #{self.max_seqno}"
  new_ids = ids.select {|seqno| seqno > self.max_seqno}
  # reset the max_seqno
  self.max_seqno = ids.max
  log "- setting max_seqno to #{self.max_seqno}"
  log "- new uids found: #{new_ids.inspect}"
  new_ids
end
clear_cached_message() click to toggle source

TODO no need for this if all shown messages are stored in SQLITE3 and keyed by UID.

# File lib/vmail/imap_client.rb, line 110
def clear_cached_message
  return unless STDIN.tty?
  log "Clearing cached message"
  current_message = nil
end
close() click to toggle source
# File lib/vmail/imap_client.rb, line 74
def close
  log "Closing connection"
  Timeout::timeout(5) do
    @imap.close rescue Net::IMAP::BadResponseError
    @imap.disconnect rescue IOError
  end
rescue Timeout::Error
end
create_if_necessary(mailbox) click to toggle source
# File lib/vmail/imap_client.rb, line 272
def create_if_necessary(mailbox)
  current_mailboxes = mailboxes.map {|m| mailbox_aliases[m] || m}
  if !current_mailboxes.include?(mailbox)
    log "Current mailboxes: #{current_mailboxes.inspect}"
    log "Creating mailbox #{mailbox}"
    log @imap.create(mailbox) 
    @mailboxes = nil # force reload ...
    list_mailboxes
  end
end
decrement_max_seqno(num) click to toggle source
# File lib/vmail/imap_client.rb, line 200
def decrement_max_seqno(num)
  return unless STDIN.tty?
  log "Decremented max seqno from #{self.max_seqno} to #{self.max_seqno - num}"
  self.max_seqno -= num
end
deliver(text) click to toggle source
# File lib/vmail/imap_client.rb, line 356
def deliver(text)
  # parse the text. The headers are yaml. The rest is text body.
  require 'net/smtp'
  prime_connection
  mail = new_mail_from_input(text)
  mail.delivery_method(*smtp_settings)
  res = mail.deliver!
  log res.inspect
  log "\n"
  msg = if res.is_a?(Mail::Message)
    "Message '#{mail.subject}' sent"
  else
    "Failed to deliver message '#{mail.subject}'!"
  end
  log msg
  msg
end
format_headers(hash) click to toggle source
# File lib/vmail/imap_client.rb, line 306
def format_headers(hash)
  lines = []
  hash.each_pair do |key, value|
    if value.nil? && key != 'to' && key != 'subject'
      next
    end
    if value.is_a?(Array)
      value = value.join(", ")
    end
    lines << "#{key.gsub("_", '-')}: #{value}"
  end
  lines.join("\n")
end
format_sent_message(mail) click to toggle source
# File lib/vmail/imap_client.rb, line 345
def format_sent_message(mail)
  formatter = Vmail::MessageFormatter.new(mail)
  message_text = Sent Message #{self.format_parts_info(formatter.list_parts)}#{format_headers(formatter.extract_headers)}#{formatter.plaintext_part}
end
forward_template() click to toggle source
# File lib/vmail/imap_client.rb, line 331
def forward_template
  original_body = current_message.plaintext.split(/\n-{20,}\n/, 2)[1]
  formatter = Vmail::MessageFormatter.new(current_mail)
  headers = formatter.extract_headers
  subject = headers['subject']
  if subject !~ /Fwd: /
    subject = "Fwd: #{subject}"
  end

  new_message_template(subject, false) + 
    "\n---------- Forwarded message ----------\n" +
    original_body + signature
end
get_highest_message_id() click to toggle source
# File lib/vmail/imap_client.rb, line 116
def get_highest_message_id
  # get highest message ID
  res = @imap.search(['ALL'])
  if res && res[-1]
    @num_messages = res[-1]
    log "Highest seqno: #@num_messages"
  else
    @num_messages = 1
    log "NO HIGHEST ID: setting @num_messages to 1"
  end
end
get_mailbox_status() click to toggle source

not used for anything

# File lib/vmail/imap_client.rb, line 129
def get_mailbox_status
  return
  @status = @imap.status(@mailbox,  ["MESSAGES", "RECENT", "UNSEEN"])
  log "Mailbox status: #{@status.inspect}"
end
handle_error(error) click to toggle source
# File lib/vmail/imap_client.rb, line 485
def handle_error(error)
  log error
end
list_mailboxes() click to toggle source
# File lib/vmail/imap_client.rb, line 156
def list_mailboxes
  log 'loading mailboxes...'
  @mailboxes ||= (@imap.list("", "*") || []).
    select {|struct| struct.attr.none? {|a| a == :Noselect} }.
    map {|struct| 
      Net::IMAP.decode_utf7(struct.name)
    }.uniq
  @mailboxes.delete("INBOX")
  @mailboxes.unshift("INBOX")
  log "Loaded mailboxes: #{@mailboxes.inspect}"
  @mailboxes = @mailboxes.map {|name| mailbox_aliases.invert[name] || name}
  @mailboxes.join("\n")
end
log(string) click to toggle source
# File lib/vmail/imap_client.rb, line 478
def log(string)
  if string.is_a?(::Net::IMAP::TaggedResponse)
    string = string.raw_data
  end
  @logger.debug string
end
mailbox_aliases() click to toggle source

do this just once

# File lib/vmail/imap_client.rb, line 171
def mailbox_aliases
  return @mailbox_aliases if @mailbox_aliases
  aliases = {"sent" => "Sent Mail",
             "all" => "All Mail",
             "starred" => "Starred",
             "important" => "Important",
             "drafts" => "Drafts",
             "spam" => "Spam",
             "trash" => "Trash"}
  @mailbox_aliases = {}
  aliases.each do |shortname, fullname|
    [ "[Gmail]", "[Google Mail]" ].each do |prefix|
      if self.mailboxes.include?( "#{prefix}/#{fullname}" )
        @mailbox_aliases[shortname] =  "#{prefix}/#{fullname}"
      end
    end
  end
  log "Setting aliases to #{@mailbox_aliases.inspect}"
  @mailbox_aliases
end
mailboxes() click to toggle source

called internally, not by vim client

# File lib/vmail/imap_client.rb, line 193
def mailboxes
  if @mailboxes.nil?
    list_mailboxes
  end
  @mailboxes
end
more_messages() click to toggle source

gets 100 messages prior to id

# File lib/vmail/imap_client.rb, line 249
def more_messages
  log "Getting more_messages"
  log "Old start_index: #{@start_index}"
  max = @start_index - 1
  @start_index = [(max + 1 - @limit), 1].max
  log "New start_index: #{@start_index}"
  fetch_ids = search_query? ? @ids[@start_index..max] : (@start_index..max).to_a
  log fetch_ids.inspect
  message_ids = fetch_and_cache_headers(fetch_ids)
  res = get_message_headers message_ids
  with_more_message_line(res)
end
new_mail_from_input(text) click to toggle source
# File lib/vmail/imap_client.rb, line 374
def new_mail_from_input(text)
  require 'mail'
  mail = Mail.new
  raw_headers, raw_body = *text.split(/\n\s*\n/, 2)
  headers = {}

  raw_headers.split("\n").each do |line|
    key, value = *line.split(/:\s*/, 2)
    if key == 'references'
      mail.references = value
    else
      next if (value.nil? || value.strip == '')
      log [key, value].join(':')
      if %(from to cc bcc).include?(key)
        value = quote_addresses(value)
      end
      headers[key] = value
    end
  end
  log "Delivering message with headers: #{headers.to_yaml}"
  mail.from = headers['from'] || @username
  mail.to = headers['to'] #.split(/,\s+/)
  mail.cc = headers['cc'] #&& headers['cc'].split(/,\s+/)
  mail.bcc = headers['bcc'] #&& headers['cc'].split(/,\s+/)
  mail.subject = headers['subject']
  mail.from ||= @username
  mail.charset = 'UTF-8'
  # attachments are added as a snippet of YAML after a blank line
  # after the headers, and followed by a blank line
  if (attachments_section = raw_body.split(/\n\s*\n/, 2)[0]) =~ /^attach(ment|ments)*:/
    files = attachments_section.split(/\n/).map {|line| line[/[-:]\s*(.*)\s*$/, 1]}.compact
    log "Attach: #{files.inspect}"
    files.each do |file|
      if File.directory?(file)
        Dir.glob("#{file}/*").each {|f| mail.add_file(f) if File.size?(f)}
      else
        mail.add_file(file) if File.size?(file)
      end
    end
    mail.text_part do
      body raw_body.split(/\n\s*\n/, 2)[1]
    end
  else
    mail.text_part do
      body raw_body
    end
  end
  mail.text_part.charset = 'UTF-8'
  mail
rescue
  $logger.debug $!
  raise
end
new_message_template(subject = nil, append_signature = true) click to toggle source
# File lib/vmail/imap_client.rb, line 295
def new_message_template(subject = nil, append_signature = true)
  #set from field to user-specified value
  headers = {'from' => "#{@name} <#{@from}>",
    'to' => nil,
    'subject' => subject,
    'cc' => @always_cc,
    'bcc' => @always_bcc
  }
  format_headers(headers) + (append_signature ? ("\n\n" + signature) : "\n\n")
end
open() click to toggle source
# File lib/vmail/imap_client.rb, line 58
def open
  @imap = Net::IMAP.new(@imap_server, @imap_port, true, nil, false)
  log @imap.login(@username, @password)
  list_mailboxes # prefetch mailbox list
rescue 
  puts "VMAIL_ERROR: #{[$!.message, $!.backtrace].join("\n")}"
end
open_html_part() click to toggle source
# File lib/vmail/imap_client.rb, line 445
def open_html_part
  log "Open_html_part"
  log current_mail.parts.inspect
  multipart = current_mail.parts.detect {|part| part.multipart?}
  html_part = if multipart 
                multipart.parts.detect {|part| part.header["Content-Type"].to_s =~ /text\/html/}
              elsif ! current_mail.parts.empty?
                current_mail.parts.detect {|part| part.header["Content-Type"].to_s =~ /text\/html/}
              else
                current_mail.body
              end
  return if html_part.nil?
  outfile = 'part.html'
  File.open(outfile, 'w') {|f| f.puts(html_part.decoded)}
  # client should handle opening the html file
  return outfile
end
prime_connection() click to toggle source
# File lib/vmail/imap_client.rb, line 142
def prime_connection
  return if @ids.nil? || @ids.empty?
  reconnect_if_necessary(4) do 
    # this is just to prime the IMAP connection
    # It's necessary for some reason before update and deliver. 
    log "Priming connection"
    res = @imap.fetch(@ids[-1], ["ENVELOPE"])
    if res.nil?
      # just go ahead, just log
      log "Priming connection didn't work, connection seems broken, but still going ahead..."
    end
  end 
end
reconnect_if_necessary(timeout = 60, &block) click to toggle source
# File lib/vmail/imap_client.rb, line 489
def reconnect_if_necessary(timeout = 60, &block)
  # if this times out, we know the connection is stale while the user is
  # trying to update
  Timeout::timeout(timeout) do
    block.call
  end
rescue IOError, Errno::EADDRNOTAVAIL, Errno::ECONNRESET, Timeout::Error, Errno::ETIMEDOUT
  log "Error: #{$!}"
  log "Attempting to reconnect"
  close
  log(revive_connection)
  # hope this isn't an endless loop
  reconnect_if_necessary do 
    block.call
  end
rescue
  log "Error: #{$!}"
  raise
end
reload_mailbox() click to toggle source
# File lib/vmail/imap_client.rb, line 103
def reload_mailbox
  return unless STDIN.tty?
  select_mailbox(@mailbox, true)
end
revive_connection() click to toggle source
# File lib/vmail/imap_client.rb, line 135
def revive_connection
  log "Reviving connection"
  open
  log "Reselecting mailbox #@mailbox"
  @imap.select(@mailbox)
end
save_attachments(dir) click to toggle source
# File lib/vmail/imap_client.rb, line 428
def save_attachments(dir)
  log "Save_attachments #{dir}"
  if !current_mail
    log "Missing a current message"
  end
  return unless dir && current_mail
  attachments = current_mail.attachments
  `mkdir -p #{dir}`
  saved = attachments.map do |x|
    path = File.join(dir, x.filename)
    log "Saving #{path}"
    File.open(path, 'wb') {|f| f.puts x.decoded}
    path
  end
  "Saved:\n" + saved.map {|x| "- #{x}"}.join("\n")
end
select_mailbox(mailbox, force=false) click to toggle source
# File lib/vmail/imap_client.rb, line 83
def select_mailbox(mailbox, force=false)
  if mailbox_aliases[mailbox]
    mailbox = mailbox_aliases[mailbox]
  end
  log "Selecting mailbox #{mailbox.inspect}"
  reconnect_if_necessary(30) do 
    log @imap.select(Net::IMAP.encode_utf7(mailbox))
  end
  log "Done"

  @mailbox = mailbox
  @label = Label[name: @mailbox] || Label.create(name: @mailbox)

  log "Getting mailbox status"
  get_mailbox_status
  log "Getting highest message id"
  get_highest_message_id
  return "OK"
end
signature() click to toggle source
# File lib/vmail/imap_client.rb, line 321
def signature
  return signature_script if @signature_script
  "\n\n#@signature"
end
signature_script() click to toggle source
# File lib/vmail/imap_client.rb, line 326
def signature_script
  return unless @signature_script
  %{ #{@signature_script.strip} }
end
smtp_settings() click to toggle source
# File lib/vmail/imap_client.rb, line 468
def smtp_settings
  [:smtp, {:address => @smtp_server,
  :port => @smtp_port,
  :domain => @smtp_domain,
  :user_name => @username,
  :password => @password,
  :authentication => @authentication,
  :enable_starttls_auto => true}]
end
spawn_thread_if_tty(&block) click to toggle source
# File lib/vmail/imap_client.rb, line 262
def spawn_thread_if_tty(&block) 
  if STDIN.tty?
    Thread.new do 
      reconnect_if_necessary(10, &block)
    end
  else
    block.call
  end
end
update() click to toggle source
# File lib/vmail/imap_client.rb, line 233
def update
  prime_connection
  new_ids = check_for_new_messages 
  if !new_ids.empty?
    @ids = @ids + new_ids
    message_ids = fetch_and_cache_headers(new_ids)
    res = get_message_headers(message_ids)
    res
  else
    ''
  end
rescue
  puts "VMAIL_ERROR: #{$!.class}\n#{[$!.message, $!.backtrace].join("\n")}"
end
window_width=(width) click to toggle source
# File lib/vmail/imap_client.rb, line 463
def window_width=(width)
  @width = width.to_i
  log "Setting window width to #{width}"
end
with_open() click to toggle source

expects a block, closes on finish

# File lib/vmail/imap_client.rb, line 67
def with_open
  @imap = Net::IMAP.new(@imap_server, @imap_port, true, nil, false)
  log @imap.login(@username, @password)
  yield self
  close
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.