vmail 1.6.8 → 1.6.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,9 +11,6 @@ module Vmail
11
11
  end
12
12
 
13
13
  def list_parts(parts = (@mail.parts.empty? ? [@mail] : @mail.parts))
14
- if parts.empty?
15
- return []
16
- end
17
14
  lines = parts.map do |part|
18
15
  if part.multipart?
19
16
  list_parts(part.parts)
@@ -25,23 +22,24 @@ module Vmail
25
22
  lines.flatten
26
23
  end
27
24
 
28
- def process_body(target = @mail)
29
- out = if target.header['Content-Type'].to_s =~ /multipart\/mixed/
30
- target.parts.map {|part|
31
- if part.multipart?
32
- part = find_text_or_html_part(target.parts)
33
- format_part(part)
34
- else
35
- format_part(part)
36
- end
37
- }.join("\n#{'-' * 39}\n")
38
- elsif target.header['Content-Type'].to_s =~ /multipart\/alternative/
39
- part = find_text_or_html_part(target.parts)
40
- format_part(part)
25
+ def plaintext_part(mail=@mail)
26
+ part = find_text_part2(mail.body, mail.content_type)
27
+ if part.nil?
28
+ "[No message body]"
41
29
  else
42
- format_part(target)
30
+ format_part part
31
+ end
32
+ end
33
+
34
+ # helper method
35
+ def find_text_part2(part, content_type)
36
+ if part.multipart?
37
+ part.parts.map {|p| find_text_part2(p, p.content_type)}.compact.first
38
+ elsif content_type =~ %r[^text/plain] ||
39
+ content_type =~ %r[text/plain] ||
40
+ content_type =~ %r[message/rfc]
41
+ part
43
42
  end
44
- out
45
43
  end
46
44
 
47
45
  def format_part(part)
@@ -53,45 +51,20 @@ module Vmail
53
51
  format_text_body(part)
54
52
  when /message\/rfc/
55
53
  m = Mail.new(part.body.decoded)
56
- process_body(m)
54
+ plaintext_part(m)
57
55
  else # just format_text on it anyway
58
56
  format_text_body(part)
59
57
  end
60
58
  else
61
- "[NO BODY]"
59
+ part.decoded.gsub("\r", '')
62
60
  end
63
61
  rescue
64
62
  puts $!
65
63
  "[error:] #{$!}"
66
64
  end
67
65
 
68
- def find_text_or_html_part(parts = @mail.parts)
69
- if parts.empty?
70
- return @mail
71
- end
72
- part = parts.detect {|part| part.multipart?}
73
- if part
74
- find_text_or_html_part(part.parts)
75
- else
76
- # no multipart part
77
- part = parts.detect {|part| (part.header["Content-Type"].to_s =~ /text\/plain/) }
78
- if part
79
- return part
80
- else
81
- parts.first
82
- end
83
- end
84
- end
85
-
86
66
  def format_text_body(part)
87
- text = part.body.decoded.gsub("\r", '')
88
- charset = (part.content_type_parameters && part.content_type_parameters['charset']) || encoding
89
- if (charset && charset != 'UTF-8')
90
- text.force_encoding(charset)
91
- else
92
- text
93
- end
94
- text.encode("UTF-8", undef: :replace, replace: "??", invalid: :replace)
67
+ part.body.decoded.gsub("\r", '')
95
68
  end
96
69
 
97
70
  # depend on lynx or whatever is set by the VMAIL_HTML_PART_READER
@@ -3,19 +3,19 @@ module Vmail
3
3
  class Query
4
4
  # args is an array like ARGV
5
5
  def self.parse(args)
6
+ args = args.dup
6
7
  if args.is_a?(String)
7
8
  args = Shellwords.shellwords args
8
9
  end
10
+ if args.size > 0 && args.first =~ /^\d+/
11
+ args.shift
12
+ end
9
13
  query = if args.empty?
10
- [100, 'ALL']
11
- elsif args.size == 1 && args[0] =~ /^\d+/
12
- [args.shift, "ALL"]
13
- elsif args[0] =~ /^\d+/
14
- args
14
+ ['ALL']
15
15
  else
16
- [100] + args
16
+ args
17
17
  end
18
- query
18
+ query.map {|x| x.to_s.downcase}
19
19
  end
20
20
 
21
21
  def self.args2string(array)
@@ -19,7 +19,7 @@ module Vmail
19
19
  end
20
20
  date = @orig_headers['date'].is_a?(String) ? Time.parse(@orig_headers['date']) : @orig_headers['date']
21
21
  quote_header = date ? "On #{date.strftime('%a, %b %d, %Y at %I:%M %p')}, #{sender} wrote:\n\n" : "#{sender} wrote:\n\n"
22
- body = quote_header + formatter.process_body
22
+ body = quote_header + formatter.plaintext_part
23
23
  begin
24
24
  body = body.gsub(/^(?=>)/, ">").gsub(/^(?!>)/, "> ")
25
25
  rescue
@@ -0,0 +1,61 @@
1
+ module Vmail
2
+ module Searching
3
+ # The main function called by the client to retrieve messages
4
+ def search(query)
5
+ @query = Vmail::Query.parse(query)
6
+ # customizable @limit is Deprecated
7
+ @limit = 100
8
+
9
+ if search_query?
10
+ #@num_messages = @all_ids.size
11
+ query_string = Vmail::Query.args2string(@query)
12
+ @ids = reconnect_if_necessary(180) do # timeout of 3 minutes
13
+ @imap.search(query_string)
14
+ end
15
+ @start_index = [@ids.size - @limit, 0].max
16
+ else
17
+ # set the target range to the whole set, unless it is too big
18
+ @start_index = [@num_messages - @limit, 0].max + 1
19
+ @query.unshift "#{@start_index}:#@num_messages"
20
+ query_string = Vmail::Query.args2string(@query)
21
+ log "Query: #{query_string.inspect}"
22
+ @ids = reconnect_if_necessary(180) do # timeout of 3 minutes
23
+ @imap.search(query_string)
24
+ end
25
+ end
26
+
27
+ if @ids.empty?
28
+ return "No messages"
29
+ end
30
+
31
+ self.max_seqno = @ids[-1] # this is a instance var
32
+ log "- Query got #{@ids.size} results; max seqno: #{self.max_seqno}"
33
+ clear_cached_message
34
+
35
+ select_ids = search_query? ? @ids[-@limit,@limit] : @ids
36
+
37
+ if select_ids.size > @limit
38
+ raise "Too many messages to fetch headers for"
39
+ end
40
+
41
+ message_ids = fetch_and_cache_headers select_ids
42
+ res = get_message_headers message_ids
43
+
44
+ if STDOUT.tty?
45
+ with_more_message_line(res)
46
+ else
47
+ # non interactive mode
48
+ puts [@mailbox, res].join("\n")
49
+ end
50
+ rescue
51
+ log "ERROR:\n#{$!.inspect}\n#{$!.backtrace.join("\n")}"
52
+ end
53
+
54
+ def search_query?
55
+ x = @query[-1] != 'all'
56
+ log "Search query? #{x} #{@query.inspect}"
57
+ x
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,103 @@
1
+ module Vmail
2
+ module ShowingHeaders
3
+
4
+ def get_message_headers(message_ids)
5
+ messages = message_ids.map {|message_id| Message[message_id] }
6
+ messages.map {|m| format_header_for_list(m)}.join("\n")
7
+ end
8
+
9
+ def fetch_and_cache_headers(id_set)
10
+ log "Fetching headers for #{id_set.size} messages"
11
+ results = reconnect_if_necessary do
12
+ @imap.fetch(id_set, ["FLAGS", "ENVELOPE", "RFC822.SIZE", "UID"])
13
+ end
14
+ results.reverse.map do |x|
15
+ envelope = x.attr["ENVELOPE"]
16
+ message_id = envelope.message_id
17
+ subject = Mail::Encodings.unquote_and_convert_to(envelope.subject, 'UTF-8')
18
+ recipients = ((envelope.to || []) + (envelope.cc || [])).map {|a| extract_address(a)}.join(', ')
19
+ sender = extract_address envelope.from.first
20
+ uid = x.attr["UID"]
21
+ message = Message[message_id]
22
+ unless message
23
+ message = Message.new
24
+ message.message_id = message_id
25
+ message.save
26
+ end
27
+ params = {
28
+ subject: (subject || ''),
29
+ flags: x.attr['FLAGS'].join(','),
30
+ date: DateTime.parse(envelope.date).to_s,
31
+ size: x.attr['RFC822.SIZE'],
32
+ sender: sender,
33
+ recipients: recipients
34
+ }
35
+ # We really just need to update the flags, buy let's update everything
36
+ message.update params
37
+
38
+ unless message.labels.include?(@label)
39
+ params = {message_id: message.message_id, uid: uid, label_id: @label.label_id}
40
+
41
+ Labeling.create params
42
+ end
43
+ message_id
44
+ end
45
+ end
46
+
47
+ def extract_address(address_struct)
48
+ address = if address_struct.nil?
49
+ "Unknown"
50
+ elsif address_struct.name
51
+ "#{Mail::Encodings.unquote_and_convert_to(address_struct.name, 'UTF-8')} <#{[address_struct.mailbox, address_struct.host].join('@')}>"
52
+ else
53
+ [Mail::Encodings.unquote_and_convert_to(address_struct.mailbox, 'UTF-8'), Mail::Encodings.unquote_and_convert_to(address_struct.host, 'UTF-8')].join('@')
54
+ end
55
+
56
+ end
57
+
58
+ def format_header_for_list(message)
59
+ date = DateTime.parse(message.date)
60
+ formatted_date = if date.year != Time.now.year
61
+ date.strftime "%b %d %Y"
62
+ else
63
+ date.strftime "%b %d %I:%M%P"
64
+ end
65
+ address = if @mailbox == mailbox_aliases['sent']
66
+ message.recipients
67
+ else
68
+ message.sender
69
+ end
70
+
71
+ mid_width = @width - 38
72
+ address_col_width = (mid_width * 0.3).ceil
73
+ subject_col_width = (mid_width * 0.7).floor
74
+ row_text = [ format_flags(message.flags).col(2),
75
+ (formatted_date || '').col(14),
76
+ address.col(address_col_width),
77
+ message.subject.col(subject_col_width),
78
+ number_to_human_size(message.size).rcol(7),
79
+ message.message_id ].join(' | ')
80
+ end
81
+
82
+ FLAGMAP = {'Flagged' => '*', 'Answered' => 'A'}
83
+
84
+ def format_flags(flags)
85
+ # other flags like "Old" should be hidden here
86
+ flags = flags.split(',').map {|flag| FLAGMAP[flag] || flag}
87
+ flags.delete("Old")
88
+ if flags.delete('Seen').nil?
89
+ flags << '+' # unread
90
+ end
91
+ flags.join('')
92
+ end
93
+
94
+ def with_more_message_line(res)
95
+ remaining = @start_index
96
+ if remaining < 1
97
+ return res
98
+ end
99
+ res + "\n> Load #{[100, remaining].min} more messages. #{remaining} remaining."
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,91 @@
1
+ module Vmail
2
+ module ShowingMessage
3
+
4
+ def current_message
5
+ return unless @cur_message_id
6
+ Message[@cur_message_id]
7
+ end
8
+
9
+ def current_mail
10
+ (c = current_message) && Mail.new(c.rfc822)
11
+ end
12
+
13
+ def cached_full_message?(message_id)
14
+ m = Message[message_id]
15
+ log "- found message #{message_id}"
16
+ log "- message has plaintext? #{!m.plaintext.nil?}"
17
+ m && !m.plaintext.nil? && m
18
+ end
19
+
20
+ def show_message(message_id, raw=false)
21
+ log "Show message: #{message_id}"
22
+ return current_message.rfc822 if raw
23
+ log "Showing message message_id: #{message_id}"
24
+ res = fetch_and_cache(message_id)
25
+ if res.nil?
26
+ # retry, though this is a hack!
27
+ log "- data is nil. retrying..."
28
+ return show_message(message_id, raw)
29
+ end
30
+ res
31
+ end
32
+
33
+ def fetch_and_cache(message_id)
34
+ if message = cached_full_message?(message_id)
35
+ log "- full message cache hit"
36
+ return message.plaintext
37
+ end
38
+ log "- full message cache miss"
39
+ labeling = Labeling[message_id: message_id, label_id: @label.label_id]
40
+ uid = labeling.uid
41
+
42
+ log "- fetching message uid #{uid}"
43
+ fetch_data = reconnect_if_necessary do
44
+ res = retry_if_needed do
45
+ @imap.uid_fetch(uid, ["FLAGS", "RFC822", "RFC822.SIZE"])
46
+ end
47
+ res[0]
48
+ end
49
+ seqno = fetch_data.seqno
50
+ rfc822 = Mail.new(fetch_data.attr['RFC822'])
51
+ formatter = Vmail::MessageFormatter.new rfc822
52
+
53
+ message = Message[message_id]
54
+ message_text = <<-EOF
55
+ #{message_id} #{number_to_human_size message.size} #{message.flags} #{format_parts_info(formatter.list_parts)}
56
+ #{divider '-'}
57
+ #{format_headers(formatter.extract_headers)}
58
+
59
+ #{formatter.plaintext_part}
60
+ EOF
61
+ # 2 calls so we can see more fine grained exceptions
62
+ message.update(:rfc822 => rfc822)
63
+ if !message_text.valid_encoding?
64
+ ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
65
+ message_text = ic.iconv(message_text)
66
+ end
67
+
68
+ begin
69
+ message.update(:plaintext => message_text)
70
+ rescue
71
+ log message_text.encoding
72
+ #log message_text
73
+ raise
74
+ end
75
+ @cur_message_id = message.message_id
76
+ message_text
77
+ rescue
78
+ msg = "Error encountered in fetch_and_cache(), message_id #{message_id} [#{@mailbox}]:\n#{$!}\n#{$!.backtrace.join("\n")}"
79
+ log msg
80
+ msg
81
+ end
82
+
83
+ def format_parts_info(parts)
84
+ lines = parts.select {|part| part !~ %r{text/plain}}
85
+ if lines.size > 0
86
+ "\n#{lines.join("\n")}"
87
+ end
88
+ end
89
+
90
+ end
91
+ end
@@ -1,3 +1,3 @@
1
1
  module Vmail
2
- VERSION = '1.6.8'
2
+ VERSION = '1.6.9'
3
3
  end
@@ -23,4 +23,6 @@ Gem::Specification.new do |s|
23
23
 
24
24
  s.add_dependency 'mail', '>= 2.2.12'
25
25
  s.add_dependency 'highline', '>= 1.6.1'
26
+ s.add_dependency 'sequel'
27
+ s.add_dependency 'sqlite3'
26
28
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: vmail
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.6.8
5
+ version: 1.6.9
6
6
  platform: ruby
7
7
  authors:
8
8
  - Daniel Choi
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-06-07 00:00:00 -04:00
13
+ date: 2011-06-28 00:00:00 -04:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -35,6 +35,28 @@ dependencies:
35
35
  version: 1.6.1
36
36
  type: :runtime
37
37
  version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: sequel
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: sqlite3
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :runtime
59
+ version_requirements: *id004
38
60
  description: Manage your email with Vim
39
61
  email:
40
62
  - dhchoi@gmail.com
@@ -49,23 +71,30 @@ extra_rdoc_files: []
49
71
  files:
50
72
  - .gitignore
51
73
  - MIT-LICENSE.txt
74
+ - NOTES
52
75
  - README.markdown
53
76
  - Rakefile
54
77
  - TODO
55
78
  - bin/vmail
56
79
  - bin/vmail_client
57
80
  - bin/vmailsend
81
+ - db/create.sql
58
82
  - lib/vmail.rb
59
83
  - lib/vmail.vim
60
84
  - lib/vmail/address_quoter.rb
61
85
  - lib/vmail/contacts_extractor.rb
86
+ - lib/vmail/database.rb
87
+ - lib/vmail/flagging_and_moving.rb
62
88
  - lib/vmail/imap_client.rb
63
89
  - lib/vmail/message_formatter.rb
64
90
  - lib/vmail/options.rb
65
91
  - lib/vmail/query.rb
66
92
  - lib/vmail/reply_template.rb
93
+ - lib/vmail/searching.rb
67
94
  - lib/vmail/send_options.rb
68
95
  - lib/vmail/sender.rb
96
+ - lib/vmail/showing_headers.rb
97
+ - lib/vmail/showing_message.rb
69
98
  - lib/vmail/string_ext.rb
70
99
  - lib/vmail/version.rb
71
100
  - test/address_quoter_test.rb