Parent

Class/Module Index [+]

Quicksearch

Redwood::Message

a Message is what's threaded.

it is also where the parsing for quotes and signatures is done, but that should be moved out to a separate class at some point (because i would like, for example, to be able to add in a ruby-talk specific module that would detect and link to /ruby-talk:d+/ sequences in the text of an email. (how sweet would that be?)

this class catches all source exceptions. if the underlying source throws an error, it is caught and handled.

Attributes

attachments[R]
bcc[R]
cc[R]
date[R]
from[R]
id[R]
labels[R]
list_address[R]
list_subscribe[R]
list_unsubscribe[R]
recipient_email[R]
refs[R]
replyto[R]
replytos[R]
snippet[R]
source[R]
source_info[R]
subj[R]
to[R]

Public Class Methods

build_from_source(source, source_info) click to toggle source
# File lib/sup/message.rb, line 338
def self.build_from_source source, source_info
  m = Message.new :source => source, :source_info => source_info
  m.load_from_source!
  m
end
new(opts) click to toggle source

if you specify a :header, will use values from that. otherwise, will try and load the header from the source.

# File lib/sup/message.rb, line 44
def initialize opts
  @source = opts[:source] or raise ArgumentError, "source can't be nil"
  @source_info = opts[:source_info] or raise ArgumentError, "source_info can't be nil"
  @snippet = opts[:snippet]
  @snippet_contains_encrypted_content = false
  @have_snippet = !(opts[:snippet].nil? || opts[:snippet].empty?)
  @labels = Set.new(opts[:labels] || [])
  @dirty = false
  @encrypted = false
  @chunks = nil
  @attachments = []

  ## we need to initialize this. see comments in parse_header as to
  ## why.
  @refs = []

  #parse_header(opts[:header] || @source.load_header(@source_info))
end
normalize_subj(s;) click to toggle source
# File lib/sup/message.rb, line 22
def normalize_subj s; s.gsub(RE_PATTERN, ""); end
reify_subj(s;) click to toggle source
# File lib/sup/message.rb, line 24
def reify_subj s; subj_is_reply?(s) ? s : "Re: " + s; end
subj_is_reply?(s;) click to toggle source
# File lib/sup/message.rb, line 23
def subj_is_reply? s; s =~ RE_PATTERN; end

Public Instance Methods

add_label(l) click to toggle source
# File lib/sup/message.rb, line 197
def add_label l
  l = l.to_sym
  return if @labels.member? l
  @labels << l
  @dirty = true
end
add_ref(ref) click to toggle source
# File lib/sup/message.rb, line 162
def add_ref ref
  @refs << ref
  @dirty = true
end
chunks() click to toggle source
# File lib/sup/message.rb, line 222
def chunks
  load_from_source!
  @chunks
end
clear_dirty() click to toggle source
# File lib/sup/message.rb, line 192
def clear_dirty
  @dirty = false
end
decode_header_field(v) click to toggle source
# File lib/sup/message.rb, line 63
def decode_header_field v
  return unless v
  return v unless v.is_a? String
  return unless v.size < MAX_HEADER_VALUE_SIZE # avoid regex blowup on spam
  Rfc2047.decode_to $encoding, Iconv.easy_decode($encoding, 'ASCII', v)
end
draft_filename() click to toggle source
# File lib/sup/message.rb, line 174
def draft_filename
  raise "not a draft" unless is_draft?
  @source.fn_for_offset @source_info
end
each_raw_message_line(&b) click to toggle source

much faster than raw_message

# File lib/sup/message.rb, line 296
def each_raw_message_line &b
  with_source_errors_handled { @source.each_raw_message_line(@source_info, &b) }
end
error_message(msg) click to toggle source
# File lib/sup/message.rb, line 256
def error_message msg
  #@snippet...*********************************************************************** An error occurred while loading this message. It is possible that the source has changed, or (in the case of remote sources) is down. You can check the log for errors, though hopefully an error window should have popped up at some point. The message location was: #@source##@source_info***********************************************************************The error message was:  #{msg}
end
has_label?(t;) click to toggle source
# File lib/sup/message.rb, line 196
def has_label? t; @labels.member? t; end
indexable_body() click to toggle source
# File lib/sup/message.rb, line 313
def indexable_body
  indexable_chunks.map { |c| c.lines }.flatten.compact.join " "
end
indexable_chunks() click to toggle source
# File lib/sup/message.rb, line 317
def indexable_chunks
  chunks.select { |c| c.is_a? Chunk::Text }
end
indexable_content() click to toggle source

returns all the content from a message that will be indexed

# File lib/sup/message.rb, line 301
def indexable_content
  load_from_source!
  [
    from && from.indexable_content,
    to.map { |p| p.indexable_content },
    cc.map { |p| p.indexable_content },
    bcc.map { |p| p.indexable_content },
    indexable_chunks.map { |c| c.lines },
    indexable_subject,
  ].flatten.compact.join " "
end
indexable_subject() click to toggle source
# File lib/sup/message.rb, line 321
def indexable_subject
  Message.normalize_subj(subj)
end
is_draft?() click to toggle source
# File lib/sup/message.rb, line 173
def is_draft?; @source.is_a? DraftLoader; end
is_list_message?() click to toggle source
# File lib/sup/message.rb, line 172
def is_list_message?; !@list_address.nil?; end
labels=(l) click to toggle source
# File lib/sup/message.rb, line 214
def labels= l
  raise ArgumentError, "not a set" unless l.is_a?(Set)
  raise ArgumentError, "not a set of labels" unless l.all? { |ll| ll.is_a?(Symbol) }
  return if @labels == l
  @labels = l
  @dirty = true
end
load_from_index!(entry) click to toggle source

Expected index entry format: :message_id, :subject => String :date => Time :refs, :replytos => Array of String :from => Person :to, :cc, :bcc => Array of Person

# File lib/sup/message.rb, line 143
def load_from_index! entry
  @id = entry[:message_id]
  @from = entry[:from]
  @date = entry[:date]
  @subj = entry[:subject]
  @to = entry[:to]
  @cc = entry[:cc]
  @bcc = entry[:bcc]
  @refs = (@refs + entry[:refs]).uniq
  @replytos = entry[:replytos]

  @replyto = nil
  @list_address = nil
  @recipient_email = nil
  @source_marked_read = false
  @list_subscribe = nil
  @list_unsubscribe = nil
end
load_from_source!() click to toggle source

this is called when the message body needs to actually be loaded.

# File lib/sup/message.rb, line 228
def load_from_source!
  @chunks ||=
    if @source.respond_to?(:has_errors?) && @source.has_errors?
      [Chunk::Text.new(error_message(@source.error.message).split("\n"))]
    else
      begin
        ## we need to re-read the header because it contains information
        ## that we don't store in the index. actually i think it's just
        ## the mailing list address (if any), so this is kinda overkill.
        ## i could just store that in the index, but i think there might
        ## be other things like that in the future, and i'd rather not
        ## bloat the index.
        ## actually, it's also the differentiation between to/cc/bcc,
        ## so i will keep this.
        rmsg = @source.load_message(@source_info)
        parse_header rmsg.header
        message_to_chunks rmsg
      rescue SourceError, SocketError => e
        warn "problem getting messages from #{@source}: #{e.message}"
        ## we need force_to_top here otherwise this window will cover
        ## up the error message one
        @source.error ||= e
        Redwood::report_broken_sources :force_to_top => true
        [Chunk::Text.new(error_message(e.message).split("\n"))]
      end
    end
end
parse_header(encoded_header) click to toggle source
# File lib/sup/message.rb, line 70
def parse_header encoded_header
  header = SavingHash.new { |k| decode_header_field encoded_header[k] }

  @id = if header["message-id"]
    mid = header["message-id"] =~ /<(.+?)>/ ? $1 : header["message-id"]
    sanitize_message_id mid
  else
    id = "sup-faked-" + Digest::MD5.hexdigest(raw_header)
    from = header["from"]
    #debug "faking non-existent message-id for message from #{from}: #{id}"
    id
  end

  @from = Person.from_address(if header["from"]
    header["from"]
  else
    name = "Sup Auto-generated Fake Sender <sup@fake.sender.example.com>"
    #debug "faking non-existent sender for message #@id: #{name}"
    name
  end)

  @date = case(date = header["date"])
  when Time
    date
  when String
    begin
      Time.parse date
    rescue ArgumentError => e
      #debug "faking mangled date header for #{@id} (orig #{header['date'].inspect} gave error: #{e.message})"
      Time.now
    end
  else
    #debug "faking non-existent date header for #{@id}"
    Time.now
  end

  @subj = header["subject"] ? header["subject"].gsub(/\s+/, " ").gsub(/\s+$/, "") : DEFAULT_SUBJECT
  @to = Person.from_address_list header["to"]
  @cc = Person.from_address_list header["cc"]
  @bcc = Person.from_address_list header["bcc"]

  ## before loading our full header from the source, we can actually
  ## have some extra refs set by the UI. (this happens when the user
  ## joins threads manually). so we will merge the current refs values
  ## in here.
  refs = (header["references"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }
  @refs = (@refs + refs).uniq
  @replytos = (header["in-reply-to"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }

  @replyto = Person.from_address header["reply-to"]
  @list_address = if header["list-post"]
    address = if header["list-post"] =~ /mailto:(.*?)[>\s$]/
      $1
    elsif header["list-post"] =~ /@/
      header["list-post"] # just try the whole fucking thing
    end
    address && Person.from_address(address)
  elsif header["x-mailing-list"]
    Person.from_address header["x-mailing-list"]
  end

  @recipient_email = header["envelope-to"] || header["x-original-to"] || header["delivered-to"]
  @source_marked_read = header["status"] == "RO"
  @list_subscribe = header["list-subscribe"]
  @list_unsubscribe = header["list-unsubscribe"]
end
quotable_body_lines() click to toggle source
# File lib/sup/message.rb, line 325
def quotable_body_lines
  chunks.find_all { |c| c.quotable? }.map { |c| c.lines }.flatten
end
quotable_header_lines() click to toggle source
# File lib/sup/message.rb, line 329
def quotable_header_lines
  ["From: #{@from.full_address}"] +
    (@to.empty? ? [] : ["To: " + @to.map { |p| p.full_address }.join(", ")]) +
    (@cc.empty? ? [] : ["Cc: " + @cc.map { |p| p.full_address }.join(", ")]) +
    (@bcc.empty? ? [] : ["Bcc: " + @bcc.map { |p| p.full_address }.join(", ")]) +
    ["Date: #{@date.rfc822}",
     "Subject: #{@subj}"]
end
raw_header() click to toggle source
# File lib/sup/message.rb, line 287
def raw_header
  with_source_errors_handled { @source.raw_header @source_info }
end
raw_message() click to toggle source
# File lib/sup/message.rb, line 291
def raw_message
  with_source_errors_handled { @source.raw_message @source_info }
end
recipients() click to toggle source
# File lib/sup/message.rb, line 210
def recipients
  @to + @cc + @bcc
end
remove_label(l) click to toggle source
# File lib/sup/message.rb, line 203
def remove_label l
  l = l.to_sym
  return unless @labels.member? l
  @labels.delete l
  @dirty = true
end
remove_ref(ref) click to toggle source
# File lib/sup/message.rb, line 167
def remove_ref ref
  @dirty = true if @refs.delete ref
end
sanitize_message_id(mid;) click to toggle source

sanitize message ids by removing spaces and non-ascii characters. also, truncate to 255 characters. all these steps are necessary to make ferret happy. of course, we probably fuck up a couple valid message ids as well. as long as we're consistent, this should be fine, though.

also, mostly the message ids that are changed by this belong to spam email.

an alternative would be to SHA1 or MD5 all message ids on a regular basis. don't tempt me.

# File lib/sup/message.rb, line 190
def sanitize_message_id mid; mid.gsub(/(\s|[^\0000-\1177])+/, "")[0..254] end
with_source_errors_handled() click to toggle source

wrap any source methods that might throw sourceerrors

# File lib/sup/message.rb, line 276
def with_source_errors_handled
  begin
    yield
  rescue SourceError => e
    warn "problem getting messages from #{@source}: #{e.message}"
    @source.error ||= e
    Redwood::report_broken_sources :force_to_top => true
    error_message e.message
  end
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.