vmail 0.3.8 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
data/bin/vmail CHANGED
File without changes
File without changes
@@ -453,6 +453,7 @@ function! s:select_mailbox()
453
453
  return
454
454
  endif
455
455
  let s:mailbox = mailbox
456
+ let s:query = "100 all"
456
457
  let command = s:select_mailbox_command . shellescape(s:mailbox)
457
458
  call system(command)
458
459
  redraw
@@ -690,9 +691,9 @@ func! s:message_window_mappings()
690
691
  nnoremap <silent> <buffer> q :close<cr>
691
692
 
692
693
  nnoremap <silent> <buffer> <leader># :close<cr>:call <SID>focus_list_window()<cr>:call <SID>delete_messages("Deleted")<cr>
693
- nmap <silent> <buffer> <leader>d <leader>#
694
694
  nnoremap <silent> <buffer> <leader>* :call <SID>focus_list_window()<cr>:call <SID>toggle_star()<cr>
695
- nmap <silent> <buffer> s <leader>*
695
+ noremap <silent> <buffer> <leader>! :call <SID>focus_list_window()<cr>:call <SID>delete_messages("[Gmail]/Spam")<CR>
696
+ noremap <silent> <buffer> <leader>e :call <SID>focus_list_window()<cr>:call <SID>archive_messages()<CR>
696
697
 
697
698
  nnoremap <silent> <buffer> <Leader>b :call <SID>focus_list_window()<cr>call <SID>move_to_mailbox(0)<CR>
698
699
  nnoremap <silent> <buffer> <Leader>B :call <SID>focus_list_window()<cr>call <SID>move_to_mailbox(1)<CR>
@@ -27,7 +27,7 @@ module Vmail
27
27
  @logger = Logger.new(config['logfile'] || STDERR)
28
28
  @logger.level = Logger::DEBUG
29
29
  @current_mail = nil
30
- @current_uid = nil
30
+ @current_id = nil
31
31
  end
32
32
 
33
33
  def open
@@ -52,9 +52,14 @@ module Vmail
52
52
  reconnect_if_necessary do
53
53
  log @imap.select(mailbox)
54
54
  end
55
+ @status = @imap.status(mailbox, ["MESSAGES", "RECENT", "UNSEEN"])
56
+ log "STATUS: #{@status.inspect}"
57
+ # get highest message ID
58
+ res = @imap.fetch([1,"*"], ["ENVELOPE"])
59
+ @num_messages = res[-1].seqno
60
+ log "HIGHEST ID: #@num_messages"
55
61
  @mailbox = mailbox
56
- @all_uids = []
57
- @bad_uids = []
62
+ @bad_ids = []
58
63
  return "OK"
59
64
  end
60
65
 
@@ -83,20 +88,19 @@ module Vmail
83
88
  @mailboxes
84
89
  end
85
90
 
86
- def fetch_headers(uid_set)
87
- if uid_set.is_a?(String)
88
- uid_set = uid_set.split(",").map(&:to_i)
89
- elsif uid_set.is_a?(Integer)
90
- uid_set = [uid_set]
91
+ # id_set may be a range, array, or string
92
+ def fetch_envelopes(id_set)
93
+ if id_set.is_a?(String)
94
+ id_set = id_set.split(',')
91
95
  end
92
- max_uid = uid_set.max
93
- log "fetch headers for #{uid_set.inspect}"
94
- if uid_set.empty?
96
+ max_id = id_set.to_a[-1]
97
+ log "fetch envelopes for #{id_set.inspect}"
98
+ if id_set.to_a.empty?
95
99
  log "empty set"
96
100
  return ""
97
101
  end
98
102
  results = reconnect_if_necessary do
99
- @imap.uid_fetch(uid_set, ["FLAGS", "ENVELOPE", "RFC822.SIZE" ])
103
+ @imap.fetch(id_set, ["FLAGS", "ENVELOPE", "RFC822.SIZE" ])
100
104
  end
101
105
  log "extracting headers"
102
106
  lines = results.
@@ -107,13 +111,13 @@ module Vmail
107
111
  Time.now
108
112
  end
109
113
  }.
110
- map {|x| format_header(x, max_uid)}
114
+ map {|x| format_list_row(x, max_id)}
111
115
  log "returning result"
112
116
  return lines.join("\n")
113
117
  end
114
118
 
115
- def format_header(fetch_data, max_uid=nil)
116
- uid = fetch_data.attr["UID"]
119
+ def format_list_row(fetch_data, max_id=nil)
120
+ id = fetch_data.seqno
117
121
  envelope = fetch_data.attr["ENVELOPE"]
118
122
  size = fetch_data.attr["RFC822.SIZE"]
119
123
  flags = fetch_data.attr["FLAGS"]
@@ -148,18 +152,18 @@ module Vmail
148
152
  subject = envelope.subject || ''
149
153
  subject = Mail::Encodings.unquote_and_convert_to(subject, 'UTF-8')
150
154
  flags = format_flags(flags)
151
- first_col_width = max_uid.to_s.length
155
+ first_col_width = max_id.to_s.length
152
156
  mid_width = @width - (first_col_width + 33)
153
157
  address_col_width = (mid_width * 0.3).ceil
154
158
  subject_col_width = (mid_width * 0.7).floor
155
- [uid.to_s.col(first_col_width),
159
+ [id.to_s.col(first_col_width),
156
160
  (date_formatted || '').col(14),
157
161
  address.col(address_col_width),
158
162
  subject.col(subject_col_width),
159
163
  number_to_human_size(size).rcol(6),
160
164
  flags.rcol(7)].join(' ')
161
165
  rescue
162
- "#{uid.to_s} : error extracting this header"
166
+ "#{id.to_s} : error extracting this header"
163
167
  end
164
168
 
165
169
  UNITS = [:b, :kb, :mb, :gb].freeze
@@ -189,17 +193,36 @@ module Vmail
189
193
  end
190
194
 
191
195
  def search(limit, *query)
192
- log "uid_search limit: #{limit} query: #{@query.inspect}"
193
- limit = 25 if limit.to_s !~ /^\d+$/
196
+ limit = limit.to_i
197
+ limit = 100 if limit.to_s !~ /^\d+$/
194
198
  query = ['ALL'] if query.empty?
195
- @query = query.join(' ')
196
- log "uid_search #@query #{limit}"
197
- @all_uids = reconnect_if_necessary do
198
- @imap.uid_search(@query)
199
+ if query.size == 1 && query[0].downcase == 'all'
200
+ # form a sequence range
201
+ query.unshift [[@num_messages - limit.to_i + 1 , 1].max, @num_messages].join(':')
202
+ @all_search = true
203
+ else
204
+ # this is a special query search
205
+ # set the target range to the whole set
206
+ query.unshift "1:#@num_messages"
207
+ @all_search = false
199
208
  end
200
- uids = @all_uids[-([limit.to_i, @all_uids.size].min)..-1] || []
201
- res = fetch_headers(uids)
202
- add_more_message_line(res, uids)
209
+ @query = query.join(' ')
210
+ log "search query: #@query"
211
+ ids = reconnect_if_necessary do
212
+ @imap.search(@query)
213
+ end
214
+ fetch_ids = if ids.size > limit
215
+ log "truncating returned set to #{limit}"
216
+ @start_index = ids.index(ids[-1]) - limit
217
+ # save ids in @ids
218
+ @ids = ids
219
+ ids[@start_index..ids[-1]]
220
+ else
221
+ ids
222
+ end
223
+ log "search query result: #{fetch_ids.inspect}"
224
+ res = fetch_envelopes(fetch_ids)
225
+ add_more_message_line(res, fetch_ids[0])
203
226
  end
204
227
 
205
228
  def update
@@ -207,65 +230,80 @@ module Vmail
207
230
  # this is just to prime the IMAP connection
208
231
  # It's necessary for some reason.
209
232
  log "priming connection for update"
210
- res = @imap.uid_fetch(@all_uids[-1], ["ENVELOPE"])
233
+ res = @imap.fetch(@all_ids[-1], ["ENVELOPE"])
211
234
  if res.nil?
212
235
  raise IOError, "IMAP connection seems broken"
213
236
  end
214
237
  end
215
- uids = reconnect_if_necessary {
216
- log "uid_search #@query"
217
- @imap.uid_search(@query)
238
+ ids = reconnect_if_necessary {
239
+ log "search #@query"
240
+ @imap.search(@query)
218
241
  }
219
- new_uids = uids - @all_uids
220
- log "UPDATE: NEW UIDS: #{new_uids.inspect}"
221
- if !new_uids.empty?
222
- res = fetch_headers(new_uids)
223
- @all_uids = uids
242
+ # TODO change this. will throw error now
243
+ new_ids = ids - @all_ids
244
+ log "UPDATE: NEW UIDS: #{new_ids.inspect}"
245
+ if !new_ids.empty?
246
+ res = fetch_envelopes(new_ids)
247
+ @all_ids = ids
224
248
  res
225
249
  end
226
250
  end
227
251
 
228
- # gets 100 messages prior to uid
229
- def more_messages(uid, limit=100)
230
- uid = uid.to_i
231
- x = [(@all_uids.index(uid) - limit), 0].max
232
- y = [@all_uids.index(uid) - 1, 0].max
233
- uids = @all_uids[x..y]
234
- res = fetch_headers(uids)
235
- add_more_message_line(res, uids)
252
+ # gets 100 messages prior to id
253
+ def more_messages(message_id, limit=100)
254
+ message_id = message_id.to_i
255
+ if @all_search
256
+ x = [(message_id - limit), 0].max
257
+ y = [message_id - 1, 0].max
258
+ res = fetch_envelopes((x..y))
259
+ add_more_message_line(res, x)
260
+ else
261
+ # filter search query
262
+ x = [(@start_index - limit), 0].max
263
+ y = [@start_index - 1, 0].max
264
+ @start_index = x
265
+ res = fetch_envelopes(@ids[x..y])
266
+ add_more_message_line(res, @ids[x])
267
+ end
236
268
  end
237
269
 
238
- def add_more_message_line(res, uids)
239
- return res if uids.empty?
240
- start_index = @all_uids.index(uids[0])
241
- if start_index > 0
242
- remaining = start_index
243
- res = "> Load #{[100, remaining].min} more messages. #{remaining} remaining.\n" + res
270
+ def add_more_message_line(res, start_id)
271
+ if @all_search
272
+ return res if start_id.nil?
273
+ if start_id <= 1
274
+ return res
275
+ end
276
+ log "remaining = start_id - 1: #{start_id} - 1"
277
+ remaining = start_id - 1
278
+ else # filter search
279
+ log "remaining = @ids.index(#{start_id}) - 1; @ids.size: #{@ids.size}"
280
+ remaining = @ids.index(start_id) - 1
244
281
  end
245
- res
282
+ log "remaining messages: #{remaining}"
283
+ "> Load #{[100, remaining].min} more messages. #{remaining} remaining.\n" + res
246
284
  end
247
285
 
248
- def show_message(uid, raw=false, forwarded=false)
249
- uid = uid.to_i
286
+ def show_message(id, raw=false, forwarded=false)
287
+ id = id.to_i
250
288
  if forwarded
251
289
  return @current_message.split(/\n-{20,}\n/, 2)[1]
252
290
  end
253
291
  return @current_mail.to_s if raw
254
- return @current_message if uid == @current_uid
255
- log "fetching #{uid.inspect}"
292
+ return @current_message if id == @current_id
293
+ log "fetching #{id.inspect}"
256
294
  fetch_data = reconnect_if_necessary do
257
- @imap.uid_fetch(uid, ["FLAGS", "RFC822", "RFC822.SIZE"])[0]
295
+ @imap.fetch(id, ["FLAGS", "RFC822", "RFC822.SIZE"])[0]
258
296
  end
259
297
  res = fetch_data.attr["RFC822"]
260
298
  mail = Mail.new(res)
261
- @current_uid = uid
299
+ @current_id = id
262
300
  @current_mail = mail # used later to show raw message or extract attachments if any
263
301
  log "saving current mail with parts: #{@current_mail.parts.inspect}"
264
302
  formatter = Vmail::MessageFormatter.new(mail)
265
303
  out = formatter.process_body
266
304
  size = fetch_data.attr["RFC822.SIZE"]
267
305
  @current_message = <<-EOF
268
- #{@mailbox} #{uid} #{number_to_human_size size} #{format_parts_info(formatter.list_parts)}
306
+ #{@mailbox} #{id} #{number_to_human_size size} #{format_parts_info(formatter.list_parts)}
269
307
  ---------------------------------------
270
308
  #{format_headers(formatter.extract_headers)}
271
309
 
@@ -283,36 +321,36 @@ EOF
283
321
  end
284
322
  end
285
323
 
286
- # uid_set is a string comming from the vim client
324
+ # id_set is a string comming from the vim client
287
325
  # action is -FLAGS or +FLAGS
288
- def flag(uid_set, action, flg)
289
- if uid_set.is_a?(String)
290
- uid_set = uid_set.split(",").map(&:to_i)
326
+ def flag(id_set, action, flg)
327
+ if id_set.is_a?(String)
328
+ id_set = id_set.split(",").map(&:to_i)
291
329
  end
292
330
  # #<struct Net::IMAP::FetchData seqno=17423, attr={"FLAGS"=>[:Seen, "Flagged"], "UID"=>83113}>
293
- log "flag #{uid_set} #{flg} #{action}"
331
+ log "flag #{id_set} #{flg} #{action}"
294
332
  if flg == 'Deleted'
295
333
  # for delete, do in a separate thread because deletions are slow
296
334
  Thread.new do
297
335
  unless @mailbox == '[Gmail]/Trash'
298
- @imap.uid_copy(uid_set, "[Gmail]/Trash")
336
+ @imap.copy(id_set, "[Gmail]/Trash")
299
337
  end
300
- res = @imap.uid_store(uid_set, action, [flg.to_sym])
338
+ res = @imap.store(id_set, action, [flg.to_sym])
301
339
  end
302
- uid_set.each { |uid| @all_uids.delete(uid) }
340
+ id_set.each { |id| @all_ids.delete(id) }
303
341
  elsif flg == '[Gmail]/Spam'
304
- @imap.uid_copy(uid_set, "[Gmail]/Spam")
305
- res = @imap.uid_store(uid_set, action, [:Deleted])
306
- "#{uid} deleted"
342
+ @imap.copy(id_set, "[Gmail]/Spam")
343
+ res = @imap.store(id_set, action, [:Deleted])
344
+ "#{id} deleted"
307
345
  else
308
346
  log "Flagging"
309
- res = @imap.uid_store(uid_set, action, [flg.to_sym])
347
+ res = @imap.store(id_set, action, [flg.to_sym])
310
348
  # log res.inspect
311
- fetch_headers(uid_set)
349
+ fetch_envelopes(id_set)
312
350
  end
313
351
  end
314
352
 
315
- def move_to(uid_set, mailbox)
353
+ def move_to(id_set, mailbox)
316
354
  if mailbox == 'all'
317
355
  log "archiving messages"
318
356
  end
@@ -320,24 +358,24 @@ EOF
320
358
  mailbox = MailboxAliases[mailbox]
321
359
  end
322
360
  create_if_necessary mailbox
323
- if uid_set.is_a?(String)
324
- uid_set = uid_set.split(",").map(&:to_i)
361
+ if id_set.is_a?(String)
362
+ id_set = id_set.split(",").map(&:to_i)
325
363
  end
326
- log "move_to #{uid_set.inspect} #{mailbox}"
327
- log @imap.uid_copy(uid_set, mailbox)
328
- log @imap.uid_store(uid_set, '+FLAGS', [:Deleted])
364
+ log "move_to #{id_set.inspect} #{mailbox}"
365
+ log @imap.copy(id_set, mailbox)
366
+ log @imap.store(id_set, '+FLAGS', [:Deleted])
329
367
  end
330
368
 
331
- def copy_to(uid_set, mailbox)
369
+ def copy_to(id_set, mailbox)
332
370
  if MailboxAliases[mailbox]
333
371
  mailbox = MailboxAliases[mailbox]
334
372
  end
335
373
  create_if_necessary mailbox
336
- log "copy #{uid_set.inspect} #{mailbox}"
337
- if uid_set.is_a?(String)
338
- uid_set = uid_set.split(",").map(&:to_i)
374
+ log "copy #{id_set.inspect} #{mailbox}"
375
+ if id_set.is_a?(String)
376
+ id_set = id_set.split(",").map(&:to_i)
339
377
  end
340
- log @imap.uid_copy(uid_set, mailbox)
378
+ log @imap.copy(id_set, mailbox)
341
379
  end
342
380
 
343
381
  def create_if_necessary(mailbox)
@@ -350,18 +388,18 @@ EOF
350
388
  end
351
389
  end
352
390
 
353
- def append_to_file(file, uid_set)
354
- if uid_set.is_a?(String)
355
- uid_set = uid_set.split(",").map(&:to_i)
391
+ def append_to_file(file, id_set)
392
+ if id_set.is_a?(String)
393
+ id_set = id_set.split(",").map(&:to_i)
356
394
  end
357
395
  log "append messages to file: #{file}"
358
- uid_set.each do |uid|
359
- message = show_message(uid)
396
+ id_set.each do |id|
397
+ message = show_message(id)
360
398
  divider = "#{'=' * 39}\n"
361
399
  File.open(file, 'a') {|f| f.puts(divider + message + "\n\n")}
362
- log "appended uid #{uid}"
400
+ log "appended id #{id}"
363
401
  end
364
- "printed #{uid_set.size} message#{uid_set.size == 1 ? '' : 's'} to #{file.strip}"
402
+ "printed #{id_set.size} message#{id_set.size == 1 ? '' : 's'} to #{file.strip}"
365
403
  end
366
404
 
367
405
 
@@ -384,9 +422,9 @@ EOF
384
422
  lines.join("\n")
385
423
  end
386
424
 
387
- def reply_template(uid, replyall=false)
388
- log "sending reply template for #{uid}"
389
- fetch_data = @imap.uid_fetch(uid.to_i, ["FLAGS", "ENVELOPE", "RFC822"])[0]
425
+ def reply_template(id, replyall=false)
426
+ log "sending reply template for #{id}"
427
+ fetch_data = @imap.fetch(id.to_i, ["FLAGS", "ENVELOPE", "RFC822"])[0]
390
428
  envelope = fetch_data.attr['ENVELOPE']
391
429
  recipient = [envelope.reply_to, envelope.from].flatten.map {|x| address_to_string(x)}[0]
392
430
  cc = [envelope.to, envelope.cc]
@@ -417,8 +455,8 @@ EOF
417
455
  "\n\n#@signature"
418
456
  end
419
457
 
420
- def forward_template(uid)
421
- original_body = show_message(uid, false, true)
458
+ def forward_template(id)
459
+ original_body = show_message(id, false, true)
422
460
  new_message_template +
423
461
  "\n---------- Forwarded message ----------\n" +
424
462
  original_body + signature
@@ -431,7 +469,7 @@ EOF
431
469
  # this is just to prime the IMAP connection
432
470
  # It's necessary for some reason.
433
471
  log "priming connection for delivering"
434
- res = @imap.uid_fetch(@all_uids[-1], ["ENVELOPE"])
472
+ res = @imap.fetch(@all_ids[-1], ["ENVELOPE"])
435
473
  if res.nil?
436
474
  raise IOError, "IMAP connection seems broken"
437
475
  end
@@ -518,8 +556,8 @@ EOF
518
556
  "saved:\n" + saved.map {|x| "- #{x}"}.join("\n")
519
557
  end
520
558
 
521
- def open_html_part(uid)
522
- log "open_html_part #{uid}"
559
+ def open_html_part(id)
560
+ log "open_html_part #{id}"
523
561
  log @current_mail.parts.inspect
524
562
  multipart = @current_mail.parts.detect {|part| part.multipart?}
525
563
  html_part = if multipart
@@ -571,6 +609,9 @@ EOF
571
609
  log(revive_connection)
572
610
  # try just once
573
611
  block.call
612
+ rescue
613
+ log "error: #{$!}"
614
+ raise
574
615
  end
575
616
 
576
617
  def self.start(config)
@@ -1,3 +1,3 @@
1
1
  module Vmail
2
- VERSION = "0.3.8"
2
+ VERSION = "0.3.9"
3
3
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 3
8
- - 8
9
- version: 0.3.8
8
+ - 9
9
+ version: 0.3.9
10
10
  platform: ruby
11
11
  authors:
12
12
  - Daniel Choi
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-15 00:00:00 -05:00
17
+ date: 2010-12-16 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency