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 +0 -0
- data/bin/vmail_client +0 -0
- data/lib/vmail.vim +3 -2
- data/lib/vmail/imap_client.rb +138 -97
- data/lib/vmail/version.rb +1 -1
- metadata +3 -3
data/bin/vmail
CHANGED
File without changes
|
data/bin/vmail_client
CHANGED
File without changes
|
data/lib/vmail.vim
CHANGED
@@ -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
|
-
|
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>
|
data/lib/vmail/imap_client.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
@
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
93
|
-
log "fetch
|
94
|
-
if
|
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.
|
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|
|
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
|
116
|
-
|
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 =
|
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
|
-
[
|
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
|
-
"#{
|
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
|
-
|
193
|
-
limit =
|
196
|
+
limit = limit.to_i
|
197
|
+
limit = 100 if limit.to_s !~ /^\d+$/
|
194
198
|
query = ['ALL'] if query.empty?
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
@
|
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
|
-
|
201
|
-
|
202
|
-
|
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.
|
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
|
-
|
216
|
-
log "
|
217
|
-
@imap.
|
238
|
+
ids = reconnect_if_necessary {
|
239
|
+
log "search #@query"
|
240
|
+
@imap.search(@query)
|
218
241
|
}
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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
|
229
|
-
def more_messages(
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
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,
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
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(
|
249
|
-
|
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
|
255
|
-
log "fetching #{
|
292
|
+
return @current_message if id == @current_id
|
293
|
+
log "fetching #{id.inspect}"
|
256
294
|
fetch_data = reconnect_if_necessary do
|
257
|
-
@imap.
|
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
|
-
@
|
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} #{
|
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
|
-
#
|
324
|
+
# id_set is a string comming from the vim client
|
287
325
|
# action is -FLAGS or +FLAGS
|
288
|
-
def flag(
|
289
|
-
if
|
290
|
-
|
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 #{
|
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.
|
336
|
+
@imap.copy(id_set, "[Gmail]/Trash")
|
299
337
|
end
|
300
|
-
res = @imap.
|
338
|
+
res = @imap.store(id_set, action, [flg.to_sym])
|
301
339
|
end
|
302
|
-
|
340
|
+
id_set.each { |id| @all_ids.delete(id) }
|
303
341
|
elsif flg == '[Gmail]/Spam'
|
304
|
-
@imap.
|
305
|
-
res = @imap.
|
306
|
-
"#{
|
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.
|
347
|
+
res = @imap.store(id_set, action, [flg.to_sym])
|
310
348
|
# log res.inspect
|
311
|
-
|
349
|
+
fetch_envelopes(id_set)
|
312
350
|
end
|
313
351
|
end
|
314
352
|
|
315
|
-
def move_to(
|
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
|
324
|
-
|
361
|
+
if id_set.is_a?(String)
|
362
|
+
id_set = id_set.split(",").map(&:to_i)
|
325
363
|
end
|
326
|
-
log "move_to #{
|
327
|
-
log @imap.
|
328
|
-
log @imap.
|
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(
|
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 #{
|
337
|
-
if
|
338
|
-
|
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.
|
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,
|
354
|
-
if
|
355
|
-
|
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
|
-
|
359
|
-
message = show_message(
|
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
|
400
|
+
log "appended id #{id}"
|
363
401
|
end
|
364
|
-
"printed #{
|
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(
|
388
|
-
log "sending reply template for #{
|
389
|
-
fetch_data = @imap.
|
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(
|
421
|
-
original_body = show_message(
|
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.
|
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(
|
522
|
-
log "open_html_part #{
|
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)
|
data/lib/vmail/version.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 3
|
8
|
-
-
|
9
|
-
version: 0.3.
|
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-
|
17
|
+
date: 2010-12-16 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|