vmail 0.3.8 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|