vmail 0.4.5 → 0.4.6
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/README.markdown +21 -0
- data/Rakefile +6 -0
- data/bin/vmail +0 -0
- data/bin/vmail_client +0 -0
- data/lib/vmail.rb +2 -0
- data/lib/vmail.vim +48 -75
- data/lib/vmail/imap_client.rb +258 -161
- data/lib/vmail/reply_template.rb +51 -0
- data/lib/vmail/version.rb +1 -1
- data/test/fixtures/reply_all.eml +58 -0
- data/test/reply_template_test.rb +32 -0
- data/website/vmail-template.html +4 -4
- metadata +7 -2
    
        data/README.markdown
    CHANGED
    
    | @@ -63,6 +63,9 @@ You can omit the password key-value pair if you'd rather not have the password | |
| 63 63 | 
             
            saved in the file. In that case, you'll prompted for the password each time you
         | 
| 64 64 | 
             
            start vmail.
         | 
| 65 65 |  | 
| 66 | 
            +
            If you are behind a firewall that blocks IMAP, there are additional
         | 
| 67 | 
            +
            configuration options that you can use. See below.
         | 
| 68 | 
            +
             | 
| 66 69 | 
             
            ## Contacts autocompletion
         | 
| 67 70 |  | 
| 68 71 | 
             
            vmail uses Vim autocompletion to help you auto-complete email addresses.
         | 
| @@ -398,6 +401,24 @@ vmail gem is downloaded from). | |
| 398 401 | 
             
            [github]:https://github.com/danchoi/vmail
         | 
| 399 402 | 
             
            [rubygems]:https://rubygems.org/gems/vmail
         | 
| 400 403 |  | 
| 404 | 
            +
            ## Additional configuration options
         | 
| 405 | 
            +
             | 
| 406 | 
            +
            The default IMAP server vmail uses is `imap.gmail.com` and the default port is
         | 
| 407 | 
            +
            `993`. If you want to change these values, e.g, because you are behind a
         | 
| 408 | 
            +
            firewall which blocks IMAP, you can change these values by adding two lines in
         | 
| 409 | 
            +
            your .vmailrc, like so:
         | 
| 410 | 
            +
             | 
| 411 | 
            +
                server: localhost
         | 
| 412 | 
            +
                port: 2999
         | 
| 413 | 
            +
             | 
| 414 | 
            +
            Then you can create an SSH tunnel, e.g.
         | 
| 415 | 
            +
             | 
| 416 | 
            +
                ssh -f user@example.com -L 2999:imap.gmail.com:993 -N
         | 
| 417 | 
            +
             | 
| 418 | 
            +
            (Thanks to [Dave Bolton][davebolton] for this patch.)
         | 
| 419 | 
            +
             | 
| 420 | 
            +
            [davebolton]:https://github.com/lightningdb
         | 
| 421 | 
            +
             | 
| 401 422 | 
             
            ## Bug reports, feature requests
         | 
| 402 423 |  | 
| 403 424 | 
             
            Please file bug reports and feature requests in the [vmail github issue tracker][tracker].
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -12,12 +12,18 @@ task :web do | |
| 12 12 | 
             
              end
         | 
| 13 13 | 
             
            end
         | 
| 14 14 |  | 
| 15 | 
            +
            desc "git push and rake release bumped version"
         | 
| 16 | 
            +
            task :bumped do
         | 
| 17 | 
            +
              puts `git commit -a -m'bump' && git push && rake release`
         | 
| 18 | 
            +
            end
         | 
| 19 | 
            +
             | 
| 15 20 | 
             
            desc "Run tests"
         | 
| 16 21 | 
             
            task :test do 
         | 
| 17 22 | 
             
              $:.unshift File.expand_path("test")
         | 
| 18 23 | 
             
              require 'test_helper'
         | 
| 19 24 | 
             
              require 'time_format_test'
         | 
| 20 25 | 
             
              require 'message_formatter_test'
         | 
| 26 | 
            +
              require 'reply_template_test'
         | 
| 21 27 | 
             
              require 'base64_test'
         | 
| 22 28 | 
             
              MiniTest::Unit.autorun
         | 
| 23 29 | 
             
            end
         | 
    
        data/bin/vmail
    CHANGED
    
    | 
            File without changes
         | 
    
        data/bin/vmail_client
    CHANGED
    
    | 
            File without changes
         | 
    
        data/lib/vmail.rb
    CHANGED
    
    
    
        data/lib/vmail.vim
    CHANGED
    
    | @@ -83,9 +83,9 @@ function! s:show_message() | |
| 83 83 | 
             
              " moving up when the next echo statement executes:
         | 
| 84 84 | 
             
              call feedkeys(":\<cr>") 
         | 
| 85 85 | 
             
              redraw
         | 
| 86 | 
            -
               | 
| 87 | 
            -
              let s: | 
| 88 | 
            -
              let command = s:show_message_command . s: | 
| 86 | 
            +
              " substract 2: because lines numbers start at 1 & messages start at line 2
         | 
| 87 | 
            +
              let s:current_message_index = line('.') - 2
         | 
| 88 | 
            +
              let command = s:show_message_command . s:current_message_index
         | 
| 89 89 | 
             
              echom "Loading message. Please wait..."
         | 
| 90 90 | 
             
              redrawstatus
         | 
| 91 91 | 
             
              let res = system(command)
         | 
| @@ -133,7 +133,7 @@ endfunction | |
| 133 133 |  | 
| 134 134 | 
             
            " invoked from withint message window
         | 
| 135 135 | 
             
            function! s:show_raw()
         | 
| 136 | 
            -
              let command = s:show_message_command . s: | 
| 136 | 
            +
              let command = s:show_message_command . s:current_message_index . ' raw'
         | 
| 137 137 | 
             
              echo command
         | 
| 138 138 | 
             
              setlocal modifiable
         | 
| 139 139 | 
             
              1,$delete
         | 
| @@ -198,29 +198,21 @@ function! s:update() | |
| 198 198 | 
             
            endfunction
         | 
| 199 199 |  | 
| 200 200 | 
             
            function! s:toggle_star() range
         | 
| 201 | 
            -
              let  | 
| 202 | 
            -
              let n = 0
         | 
| 203 | 
            -
              let uids = []
         | 
| 204 | 
            -
              while lnum <= a:lastline
         | 
| 205 | 
            -
                let line =  getline(lnum)
         | 
| 206 | 
            -
                let message_uid = matchstr(line, '^\d\+')
         | 
| 207 | 
            -
                call add(uids, message_uid)
         | 
| 208 | 
            -
                let lnum = lnum + 1
         | 
| 209 | 
            -
              endwhile
         | 
| 210 | 
            -
              let uid_set = join(uids, ",")
         | 
| 201 | 
            +
              let uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
         | 
| 211 202 | 
             
              let flag_symbol = "[*]"
         | 
| 212 203 | 
             
              " check if starred already
         | 
| 213 204 | 
             
              let action = " +FLAGS"
         | 
| 214 | 
            -
              if (match( | 
| 205 | 
            +
              if (match(getline(a:firstline), flag_symbol) != -1)
         | 
| 215 206 | 
             
                let action = " -FLAGS"
         | 
| 216 207 | 
             
              endif
         | 
| 217 208 | 
             
              let command = s:flag_command . uid_set . action . " Flagged" 
         | 
| 218 | 
            -
              if  | 
| 219 | 
            -
                echom "toggling flag on message | 
| 209 | 
            +
              if (a:lastline - a:firstline + 1) == 1
         | 
| 210 | 
            +
                echom "toggling flag on message" 
         | 
| 220 211 | 
             
              else
         | 
| 221 | 
            -
                echom "toggling flags on  | 
| 212 | 
            +
                echom "toggling flags on " . (a:lastline - a:firstline + 1) . " messages"
         | 
| 222 213 | 
             
              endif
         | 
| 223 | 
            -
              " toggle [*] on lines
         | 
| 214 | 
            +
              " toggle [*] on lines; TODO: do this all in vimscript and do the starring
         | 
| 215 | 
            +
              " in a thread in imap_client
         | 
| 224 216 | 
             
              let res = system(command)
         | 
| 225 217 | 
             
              setlocal modifiable
         | 
| 226 218 | 
             
              exec a:firstline . "," . a:lastline . "delete"
         | 
| @@ -232,22 +224,19 @@ function! s:toggle_star() range | |
| 232 224 | 
             
              if len(split(res, "\n")) > 2
         | 
| 233 225 | 
             
                call feedkeys("\<cr>")
         | 
| 234 226 | 
             
              endif
         | 
| 227 | 
            +
              echom "done" 
         | 
| 235 228 | 
             
            endfunction
         | 
| 236 229 |  | 
| 237 230 | 
             
            " flag can be Deleted or [Gmail]/Spam
         | 
| 238 231 | 
             
            func! s:delete_messages(flag) range
         | 
| 239 | 
            -
              let  | 
| 240 | 
            -
              let n = 0
         | 
| 241 | 
            -
              let uids = []
         | 
| 242 | 
            -
              while lnum <= a:lastline
         | 
| 243 | 
            -
                let line =  getline(lnum)
         | 
| 244 | 
            -
                let message_uid = matchstr(line, '^\d\+')
         | 
| 245 | 
            -
                call add(uids, message_uid)
         | 
| 246 | 
            -
                let lnum = lnum + 1
         | 
| 247 | 
            -
              endwhile
         | 
| 248 | 
            -
              let uid_set = join(uids, ",")
         | 
| 232 | 
            +
              let uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
         | 
| 249 233 | 
             
              let command = s:flag_command . uid_set . " +FLAGS " . a:flag
         | 
| 250 | 
            -
               | 
| 234 | 
            +
              let nummsgs = (a:lastline - a:firstline + 1)
         | 
| 235 | 
            +
              if nummsgs == 1
         | 
| 236 | 
            +
                echom "deleting message" 
         | 
| 237 | 
            +
              else
         | 
| 238 | 
            +
                echom "deleting " . (a:lastline - a:firstline + 1) . " messages"
         | 
| 239 | 
            +
              endif
         | 
| 251 240 | 
             
              let res = system(command)
         | 
| 252 241 | 
             
              setlocal modifiable
         | 
| 253 242 | 
             
              exec a:firstline . "," . a:lastline . "delete"
         | 
| @@ -255,53 +244,39 @@ func! s:delete_messages(flag) range | |
| 255 244 | 
             
              write
         | 
| 256 245 | 
             
              " if more than 2 lines change, vim forces us to look at a message.
         | 
| 257 246 | 
             
              " dismiss it.
         | 
| 258 | 
            -
              if  | 
| 247 | 
            +
              if nummsgs > 2
         | 
| 259 248 | 
             
                call feedkeys("\<cr>")
         | 
| 260 249 | 
             
              endif
         | 
| 261 250 | 
             
              redraw
         | 
| 262 | 
            -
              echo  | 
| 251 | 
            +
              echo nummsgs .  " message" . (nummsgs == 1 ? '' : 's') . " marked " . a:flag
         | 
| 263 252 | 
             
            endfunc
         | 
| 264 253 |  | 
| 265 254 | 
             
            func! s:archive_messages() range
         | 
| 266 | 
            -
              let  | 
| 267 | 
            -
              let n = 0
         | 
| 268 | 
            -
              let uids = []
         | 
| 269 | 
            -
              while lnum <= a:lastline
         | 
| 270 | 
            -
                let line =  getline(lnum)
         | 
| 271 | 
            -
                let message_uid = matchstr(line, '^\d\+')
         | 
| 272 | 
            -
                call add(uids, message_uid)
         | 
| 273 | 
            -
                let lnum = lnum + 1
         | 
| 274 | 
            -
              endwhile
         | 
| 275 | 
            -
              let uid_set = join(uids, ",")
         | 
| 255 | 
            +
              let uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
         | 
| 276 256 | 
             
              let command = s:move_to_command . uid_set . ' ' . "all"
         | 
| 277 | 
            -
               | 
| 257 | 
            +
              let nummsgs = (a:lastline - a:firstline + 1)
         | 
| 258 | 
            +
              echo "archiving message" . (nummsgs == 1 ? '' : 's')
         | 
| 278 259 | 
             
              let res = system(command)
         | 
| 279 260 | 
             
              setlocal modifiable
         | 
| 280 261 | 
             
              exec a:firstline . "," . a:lastline . "delete"
         | 
| 281 262 | 
             
              setlocal nomodifiable
         | 
| 282 263 | 
             
              write
         | 
| 283 | 
            -
               | 
| 284 | 
            -
             | 
| 264 | 
            +
              if nummsgs > 2
         | 
| 265 | 
            +
                call feedkeys("\<cr>")
         | 
| 266 | 
            +
              endif
         | 
| 285 267 | 
             
              redraw
         | 
| 286 | 
            -
              echo  | 
| 268 | 
            +
              echo nummsgs . " message" . (nummsgs == 1 ? '' : 's') . " archived"
         | 
| 287 269 | 
             
            endfunc
         | 
| 288 270 |  | 
| 289 271 | 
             
            " --------------------------------------------------------------------------------
         | 
| 272 | 
            +
             | 
| 290 273 | 
             
            " append text bodies of a set of messages to a file
         | 
| 291 274 | 
             
            func! s:append_messages_to_file() range
         | 
| 292 | 
            -
              let  | 
| 293 | 
            -
              let  | 
| 294 | 
            -
              let uids = []
         | 
| 295 | 
            -
              while lnum <= a:lastline
         | 
| 296 | 
            -
                let line =  getline(lnum)
         | 
| 297 | 
            -
                let message_uid = matchstr(line, '^\d\+')
         | 
| 298 | 
            -
                call add(uids, message_uid)
         | 
| 299 | 
            -
                let lnum = lnum + 1
         | 
| 300 | 
            -
              endwhile
         | 
| 301 | 
            -
              let uid_set = join(uids, ",")
         | 
| 275 | 
            +
              let uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
         | 
| 276 | 
            +
              let nummsgs = (a:lastline - a:firstline + 1)
         | 
| 302 277 | 
             
              let s:append_file = input("print messages to file: ", s:append_file)
         | 
| 303 278 | 
             
              let command = s:append_to_file_command . s:append_file . ' ' . uid_set 
         | 
| 304 | 
            -
              echo "appending " .  | 
| 279 | 
            +
              echo "appending " . nummsgs . " message" . (nummsgs == 1 ? '' : 's') . " to " . s:append_file . ". please wait..."
         | 
| 305 280 | 
             
              let res = system(command)
         | 
| 306 281 | 
             
              echo res
         | 
| 307 282 | 
             
              redraw
         | 
| @@ -311,16 +286,8 @@ endfunc | |
| 311 286 | 
             
            " move to another mailbox
         | 
| 312 287 | 
             
            function! s:move_to_mailbox(copy) range
         | 
| 313 288 | 
             
              let s:copy_to_mailbox = a:copy
         | 
| 314 | 
            -
              let  | 
| 315 | 
            -
              let  | 
| 316 | 
            -
              let uids = []
         | 
| 317 | 
            -
              while lnum <= a:lastline
         | 
| 318 | 
            -
                let line =  getline(lnum)
         | 
| 319 | 
            -
                let message_uid = matchstr(line, '^\d\+')
         | 
| 320 | 
            -
                call add(uids, message_uid)
         | 
| 321 | 
            -
                let lnum = lnum + 1
         | 
| 322 | 
            -
              endwhile
         | 
| 323 | 
            -
              let s:uid_set = join(uids, ",")
         | 
| 289 | 
            +
              let s:uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
         | 
| 290 | 
            +
              let s:nummsgs = (a:lastline - a:firstline + 1)
         | 
| 324 291 | 
             
              " now prompt use to select mailbox
         | 
| 325 292 | 
             
              if !exists("s:mailboxes")
         | 
| 326 293 | 
             
                call s:get_mailbox_list()
         | 
| @@ -360,8 +327,11 @@ function! s:complete_move_to_mailbox() | |
| 360 327 | 
             
              end
         | 
| 361 328 | 
             
              setlocal nomodifiable
         | 
| 362 329 | 
             
              write
         | 
| 330 | 
            +
              if s:nummsgs > 2
         | 
| 331 | 
            +
                call feedkeys("\<cr>")
         | 
| 332 | 
            +
              endif
         | 
| 363 333 | 
             
              redraw
         | 
| 364 | 
            -
              echo " | 
| 334 | 
            +
              echo s:nummsgs .  " message" . (s:nummsgs == 1 ? '' : 's') . ' ' . (s:copy_to_mailbox ? 'copied' : 'moved') . ' to ' . mailbox 
         | 
| 365 335 | 
             
            endfunction
         | 
| 366 336 |  | 
| 367 337 | 
             
            function! CompleteMoveMailbox(findstart, base)
         | 
| @@ -453,14 +423,17 @@ function! s:select_mailbox() | |
| 453 423 | 
             
                return
         | 
| 454 424 | 
             
              endif
         | 
| 455 425 | 
             
              let s:mailbox = mailbox
         | 
| 426 | 
            +
              let s:query = "100 all"
         | 
| 456 427 | 
             
              let command = s:select_mailbox_command . shellescape(s:mailbox)
         | 
| 428 | 
            +
              echo "selecting mailbox ". s:mailbox ". please wait..."
         | 
| 429 | 
            +
             | 
| 457 430 | 
             
              call system(command)
         | 
| 458 431 | 
             
              redraw
         | 
| 459 432 | 
             
              " now get latest 100 messages
         | 
| 460 433 | 
             
              call s:focus_list_window()  
         | 
| 461 434 | 
             
              setlocal modifiable
         | 
| 462 435 | 
             
              let command = s:search_command . "100 all"
         | 
| 463 | 
            -
              echo " | 
| 436 | 
            +
              echo "loading messages..."
         | 
| 464 437 | 
             
              let res = system(command)
         | 
| 465 438 | 
             
              1,$delete
         | 
| 466 439 | 
             
              put! =res
         | 
| @@ -514,7 +487,7 @@ function! s:more_messages() | |
| 514 487 | 
             
              let line = getline(line('.'))
         | 
| 515 488 | 
             
              let uid = matchstr(line, '^\d\+')
         | 
| 516 489 | 
             
              let command = s:more_messages_command . uid
         | 
| 517 | 
            -
              echo  | 
| 490 | 
            +
              echo "fetching more messages. please wait..."
         | 
| 518 491 | 
             
              let res = system(command)
         | 
| 519 492 | 
             
              setlocal modifiable
         | 
| 520 493 | 
             
              let lines =  split(res, "\n")
         | 
| @@ -528,7 +501,7 @@ endfunction | |
| 528 501 | 
             
            "  compose reply, compose, forward, save draft
         | 
| 529 502 |  | 
| 530 503 | 
             
            function! s:compose_reply(all)
         | 
| 531 | 
            -
              let command = s:reply_template_command  | 
| 504 | 
            +
              let command = s:reply_template_command 
         | 
| 532 505 | 
             
              if a:all
         | 
| 533 506 | 
             
                let command = command . ' 1'
         | 
| 534 507 | 
             
              endif
         | 
| @@ -546,7 +519,7 @@ function! s:compose_message() | |
| 546 519 | 
             
            endfunction
         | 
| 547 520 |  | 
| 548 521 | 
             
            function! s:compose_forward()
         | 
| 549 | 
            -
              let command = s:forward_template_command  | 
| 522 | 
            +
              let command = s:forward_template_command 
         | 
| 550 523 | 
             
              call s:open_compose_window(command)
         | 
| 551 524 | 
             
            "  call search("^to:") 
         | 
| 552 525 | 
             
            "  normal A
         | 
| @@ -628,7 +601,7 @@ endfunc | |
| 628 601 |  | 
| 629 602 | 
             
            " call from inside message window with <Leader>h
         | 
| 630 603 | 
             
            func! s:open_html_part()
         | 
| 631 | 
            -
              let command = s:open_html_part_command  | 
| 604 | 
            +
              let command = s:open_html_part_command 
         | 
| 632 605 | 
             
              " the command saves the html part to a local file
         | 
| 633 606 | 
             
              let outfile = system(command)
         | 
| 634 607 | 
             
              " todo: allow user to change open in browser command?
         | 
| @@ -690,9 +663,9 @@ func! s:message_window_mappings() | |
| 690 663 | 
             
              nnoremap <silent> <buffer> q :close<cr>
         | 
| 691 664 |  | 
| 692 665 | 
             
              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 666 | 
             
              nnoremap <silent> <buffer> <leader>*  :call <SID>focus_list_window()<cr>:call <SID>toggle_star()<cr>
         | 
| 695 | 
            -
               | 
| 667 | 
            +
              noremap <silent> <buffer> <leader>! :call <SID>focus_list_window()<cr>:call <SID>delete_messages("[Gmail]/Spam")<CR>
         | 
| 668 | 
            +
              noremap <silent> <buffer> <leader>e :call <SID>focus_list_window()<cr>:call <SID>archive_messages()<CR>
         | 
| 696 669 |  | 
| 697 670 | 
             
              nnoremap <silent> <buffer> <Leader>b :call <SID>focus_list_window()<cr>call <SID>move_to_mailbox(0)<CR>
         | 
| 698 671 | 
             
              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
    
    | @@ -1,5 +1,6 @@ | |
| 1 1 | 
             
            require 'drb'
         | 
| 2 2 | 
             
            require 'vmail/message_formatter'
         | 
| 3 | 
            +
            require 'vmail/reply_template'
         | 
| 3 4 | 
             
            require 'vmail/string_ext'
         | 
| 4 5 | 
             
            require 'yaml'
         | 
| 5 6 | 
             
            require 'mail'
         | 
| @@ -27,11 +28,13 @@ module Vmail | |
| 27 28 | 
             
                  @logger = Logger.new(config['logfile'] || STDERR)
         | 
| 28 29 | 
             
                  @logger.level = Logger::DEBUG
         | 
| 29 30 | 
             
                  @current_mail = nil
         | 
| 30 | 
            -
                  @ | 
| 31 | 
            +
                  @current_message_index = nil
         | 
| 32 | 
            +
                  @imap_server = config['server'] || 'imap.gmail.com'
         | 
| 33 | 
            +
                  @imap_port = config['port'] || 993
         | 
| 31 34 | 
             
                end
         | 
| 32 35 |  | 
| 33 36 | 
             
                def open
         | 
| 34 | 
            -
                  @imap = Net::IMAP.new( | 
| 37 | 
            +
                  @imap = Net::IMAP.new(@imap_server, @imap_port, true, nil, false)
         | 
| 35 38 | 
             
                  @imap.login(@username, @password)
         | 
| 36 39 | 
             
                end
         | 
| 37 40 |  | 
| @@ -41,11 +44,11 @@ module Vmail | |
| 41 44 | 
             
                  @imap.disconnect
         | 
| 42 45 | 
             
                end
         | 
| 43 46 |  | 
| 44 | 
            -
                def select_mailbox(mailbox)
         | 
| 47 | 
            +
                def select_mailbox(mailbox, force=false)
         | 
| 45 48 | 
             
                  if MailboxAliases[mailbox]
         | 
| 46 49 | 
             
                    mailbox = MailboxAliases[mailbox]
         | 
| 47 50 | 
             
                  end
         | 
| 48 | 
            -
                  if mailbox == @mailbox 
         | 
| 51 | 
            +
                  if mailbox == @mailbox && !force
         | 
| 49 52 | 
             
                    return
         | 
| 50 53 | 
             
                  end
         | 
| 51 54 | 
             
                  log "selecting mailbox #{mailbox.inspect}"
         | 
| @@ -53,11 +56,27 @@ module Vmail | |
| 53 56 | 
             
                    log @imap.select(mailbox)
         | 
| 54 57 | 
             
                  end
         | 
| 55 58 | 
             
                  @mailbox = mailbox
         | 
| 56 | 
            -
                   | 
| 57 | 
            -
                   | 
| 59 | 
            +
                  get_mailbox_status
         | 
| 60 | 
            +
                  get_highest_message_id
         | 
| 58 61 | 
             
                  return "OK"
         | 
| 59 62 | 
             
                end
         | 
| 60 63 |  | 
| 64 | 
            +
                def reload_mailbox
         | 
| 65 | 
            +
                  select_mailbox(@mailbox, true)
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def get_highest_message_id
         | 
| 69 | 
            +
                  # get highest message ID
         | 
| 70 | 
            +
                  res = @imap.fetch([1,"*"], ["ENVELOPE"])
         | 
| 71 | 
            +
                  @num_messages = res[-1].seqno
         | 
| 72 | 
            +
                  log "HIGHEST ID: #@num_messages"
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def get_mailbox_status
         | 
| 76 | 
            +
                  @status = @imap.status(@mailbox,  ["MESSAGES", "RECENT", "UNSEEN"])
         | 
| 77 | 
            +
                  log "mailbox status: #{@status.inspect}"
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 61 80 | 
             
                def revive_connection
         | 
| 62 81 | 
             
                  log "reviving connection"
         | 
| 63 82 | 
             
                  open
         | 
| @@ -65,6 +84,18 @@ module Vmail | |
| 65 84 | 
             
                  @imap.select(@mailbox)
         | 
| 66 85 | 
             
                end
         | 
| 67 86 |  | 
| 87 | 
            +
                def prime_connection
         | 
| 88 | 
            +
                  reconnect_if_necessary(4) do 
         | 
| 89 | 
            +
                    # this is just to prime the IMAP connection
         | 
| 90 | 
            +
                    # It's necessary for some reason before update and deliver. 
         | 
| 91 | 
            +
                    log "priming connection for delivering"
         | 
| 92 | 
            +
                    res = @imap.fetch(@ids[-1], ["ENVELOPE"])
         | 
| 93 | 
            +
                    if res.nil?
         | 
| 94 | 
            +
                      raise IOError, "IMAP connection seems broken"
         | 
| 95 | 
            +
                    end
         | 
| 96 | 
            +
                  end 
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 68 99 | 
             
                def list_mailboxes
         | 
| 69 100 | 
             
                  @mailboxes ||= (@imap.list("[Gmail]/", "%") + @imap.list("", "%")).
         | 
| 70 101 | 
             
                    select {|struct| struct.attr.none? {|a| a == :Noselect} }.
         | 
| @@ -83,37 +114,52 @@ module Vmail | |
| 83 114 | 
             
                  @mailboxes
         | 
| 84 115 | 
             
                end
         | 
| 85 116 |  | 
| 86 | 
            -
                 | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
                   | 
| 90 | 
            -
                     | 
| 117 | 
            +
                # id_set may be a range, array, or string
         | 
| 118 | 
            +
                def fetch_envelopes(id_set, are_uids=false)
         | 
| 119 | 
            +
                  log "fetch_envelopes: #{id_set.inspect}"
         | 
| 120 | 
            +
                  if id_set.is_a?(String)
         | 
| 121 | 
            +
                    id_set = id_set.split(',')
         | 
| 91 122 | 
             
                  end
         | 
| 92 | 
            -
                   | 
| 93 | 
            -
                   | 
| 94 | 
            -
                  if uid_set.empty?
         | 
| 123 | 
            +
                  max_id = id_set.to_a[-1]
         | 
| 124 | 
            +
                  if id_set.to_a.empty?
         | 
| 95 125 | 
             
                    log "empty set"
         | 
| 96 126 | 
             
                    return ""
         | 
| 97 127 | 
             
                  end
         | 
| 98 128 | 
             
                  results = reconnect_if_necessary do 
         | 
| 99 | 
            -
                     | 
| 129 | 
            +
                    if are_uids
         | 
| 130 | 
            +
                      @imap.uid_fetch(id_set, ["FLAGS", "ENVELOPE", "RFC822.SIZE", "UID" ])
         | 
| 131 | 
            +
                    else
         | 
| 132 | 
            +
                      @imap.fetch(id_set, ["FLAGS", "ENVELOPE", "RFC822.SIZE", "UID" ])
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                  if results.nil?
         | 
| 136 | 
            +
                    error = "expected fetch results but got nil"
         | 
| 137 | 
            +
                    log(error) && raise(error)
         | 
| 100 138 | 
             
                  end
         | 
| 101 139 | 
             
                  log "extracting headers"
         | 
| 102 | 
            -
                   | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
                       | 
| 107 | 
            -
                         | 
| 108 | 
            -
             | 
| 109 | 
            -
                    } | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 140 | 
            +
                  new_message_rows = results.map {|x| extract_row_data(x, max_id) }
         | 
| 141 | 
            +
                  if are_uids
         | 
| 142 | 
            +
                    # replace old row_text values
         | 
| 143 | 
            +
                    new_message_rows.each {|new_row_data|
         | 
| 144 | 
            +
                      @message_list.
         | 
| 145 | 
            +
                        select {|old_row_data| old_row_data[:uid] == new_row_data[:uid]}.
         | 
| 146 | 
            +
                        each {|old_row_data| old_row_data[:row_text] = new_row_data[:row_text]}
         | 
| 147 | 
            +
                    }
         | 
| 148 | 
            +
                  else
         | 
| 149 | 
            +
                    # put new rows before the current ones
         | 
| 150 | 
            +
                    @message_list.unshift(*new_message_rows)
         | 
| 151 | 
            +
                  end
         | 
| 152 | 
            +
                  log "returning #{new_message_rows.size} new rows" 
         | 
| 153 | 
            +
                  return new_message_rows.
         | 
| 154 | 
            +
                    map {|x| x[:row_text]}.
         | 
| 155 | 
            +
                    join("\n")
         | 
| 113 156 | 
             
                end
         | 
| 114 157 |  | 
| 115 | 
            -
                 | 
| 116 | 
            -
             | 
| 158 | 
            +
                # TODO extract this to another class or module and write unit tests
         | 
| 159 | 
            +
                def extract_row_data(fetch_data, max_id=nil)
         | 
| 160 | 
            +
                  seqno = fetch_data.seqno
         | 
| 161 | 
            +
                  uid = fetch_data.attr['UID']
         | 
| 162 | 
            +
                  # log "fetched seqno #{seqno} uid #{uid}"
         | 
| 117 163 | 
             
                  envelope = fetch_data.attr["ENVELOPE"]
         | 
| 118 164 | 
             
                  size = fetch_data.attr["RFC822.SIZE"]
         | 
| 119 165 | 
             
                  flags = fetch_data.attr["FLAGS"]
         | 
| @@ -148,18 +194,22 @@ module Vmail | |
| 148 194 | 
             
                  subject = envelope.subject || ''
         | 
| 149 195 | 
             
                  subject = Mail::Encodings.unquote_and_convert_to(subject, 'UTF-8')
         | 
| 150 196 | 
             
                  flags = format_flags(flags)
         | 
| 151 | 
            -
                  first_col_width =  | 
| 197 | 
            +
                  first_col_width = max_id.to_s.length 
         | 
| 152 198 | 
             
                  mid_width = @width - (first_col_width + 33)
         | 
| 153 199 | 
             
                  address_col_width = (mid_width * 0.3).ceil
         | 
| 154 200 | 
             
                  subject_col_width = (mid_width * 0.7).floor
         | 
| 155 | 
            -
                  [ | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 201 | 
            +
                  row_text = [  seqno.to_s.col(first_col_width), 
         | 
| 202 | 
            +
                                (date_formatted || '').col(14),
         | 
| 203 | 
            +
                                address.col(address_col_width),
         | 
| 204 | 
            +
                                subject.col(subject_col_width),
         | 
| 205 | 
            +
                                number_to_human_size(size).rcol(6),
         | 
| 206 | 
            +
                                flags.rcol(7)  
         | 
| 207 | 
            +
                             ].join(' ')
         | 
| 208 | 
            +
                  {:uid => uid, :seqno => seqno, :row_text => row_text}
         | 
| 161 209 | 
             
                rescue 
         | 
| 162 | 
            -
                  "#{uid | 
| 210 | 
            +
                  log "error extracting header for uid #{uid} seqno #{seqno}: #$!"
         | 
| 211 | 
            +
                  row_text =i "#{seqno.to_s} : error extracting this header"
         | 
| 212 | 
            +
                  {:uid => uid, :seqno => seqno, :row_text => row_text}
         | 
| 163 213 | 
             
                end
         | 
| 164 214 |  | 
| 165 215 | 
             
                UNITS = [:b, :kb, :mb, :gb].freeze
         | 
| @@ -189,83 +239,120 @@ module Vmail | |
| 189 239 | 
             
                end
         | 
| 190 240 |  | 
| 191 241 | 
             
                def search(limit, *query)
         | 
| 192 | 
            -
                   | 
| 193 | 
            -
                  limit =  | 
| 242 | 
            +
                  limit = limit.to_i
         | 
| 243 | 
            +
                  limit = 100 if limit.to_s !~ /^\d+$/
         | 
| 194 244 | 
             
                  query = ['ALL'] if query.empty?
         | 
| 195 | 
            -
                   | 
| 196 | 
            -
             | 
| 197 | 
            -
             | 
| 198 | 
            -
                    @ | 
| 199 | 
            -
                   | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 245 | 
            +
                  if query.size == 1 && query[0].downcase == 'all'
         | 
| 246 | 
            +
                    # form a sequence range
         | 
| 247 | 
            +
                    query.unshift [[@num_messages - limit.to_i + 1 , 1].max, @num_messages].join(':')
         | 
| 248 | 
            +
                    @all_search = true
         | 
| 249 | 
            +
                  else
         | 
| 250 | 
            +
                    # this is a special query search
         | 
| 251 | 
            +
                    # set the target range to the whole set
         | 
| 252 | 
            +
                    query.unshift "1:#@num_messages"
         | 
| 253 | 
            +
                    @all_search = false
         | 
| 254 | 
            +
                  end
         | 
| 255 | 
            +
                  log "@all_search #{@all_search}"
         | 
| 256 | 
            +
                  @query = query
         | 
| 257 | 
            +
                  log "search query: #@query.inspect"
         | 
| 258 | 
            +
                  @ids = reconnect_if_necessary do
         | 
| 259 | 
            +
                    @imap.search(@query.join(' '))
         | 
| 260 | 
            +
                  end
         | 
| 261 | 
            +
                  # save ids in @ids, because filtered search relies on it
         | 
| 262 | 
            +
                  fetch_ids = if @all_search
         | 
| 263 | 
            +
                                @ids
         | 
| 264 | 
            +
                              else #filtered search
         | 
| 265 | 
            +
                                @start_index = [@ids.length - limit, 0].max
         | 
| 266 | 
            +
                                @ids[@start_index..-1]
         | 
| 267 | 
            +
                              end
         | 
| 268 | 
            +
                  log "search query got #{@ids.size} results" 
         | 
| 269 | 
            +
                  @message_list = [] # this will hold all the data extracted from these message envelopes
         | 
| 270 | 
            +
                  res = fetch_envelopes(fetch_ids)
         | 
| 271 | 
            +
                  add_more_message_line(res, fetch_ids[0])
         | 
| 203 272 | 
             
                end
         | 
| 204 273 |  | 
| 205 274 | 
             
                def update
         | 
| 206 | 
            -
                   | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 216 | 
            -
                    log "uid_search #@query"
         | 
| 217 | 
            -
                    @imap.uid_search(@query) 
         | 
| 275 | 
            +
                  prime_connection
         | 
| 276 | 
            +
                  old_num_messages = @num_messages
         | 
| 277 | 
            +
                  # we need to re-select the mailbox to get the new highest id
         | 
| 278 | 
            +
                  reload_mailbox
         | 
| 279 | 
            +
                  update_query = @query
         | 
| 280 | 
            +
                  # set a new range filter
         | 
| 281 | 
            +
                  update_query[0] = "#{old_num_messages}:#{@num_messages}"
         | 
| 282 | 
            +
                  ids = reconnect_if_necessary { 
         | 
| 283 | 
            +
                    log "search #update_query"
         | 
| 284 | 
            +
                    @imap.search(update_query.join(' ')) 
         | 
| 218 285 | 
             
                  }
         | 
| 219 | 
            -
                   | 
| 220 | 
            -
                   | 
| 221 | 
            -
                   | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 286 | 
            +
                  # TODO change this. will throw error now
         | 
| 287 | 
            +
                  new_ids = ids.select {|x| x > @ids.max}
         | 
| 288 | 
            +
                  @ids = @ids + new_ids
         | 
| 289 | 
            +
                  log "UPDATE: NEW UIDS: #{new_ids.inspect}"
         | 
| 290 | 
            +
                  if !new_ids.empty?
         | 
| 291 | 
            +
                    res = fetch_envelopes(new_ids)
         | 
| 224 292 | 
             
                    res
         | 
| 225 293 | 
             
                  end
         | 
| 226 294 | 
             
                end
         | 
| 227 295 |  | 
| 228 | 
            -
                # gets 100 messages prior to  | 
| 229 | 
            -
                def more_messages( | 
| 230 | 
            -
                   | 
| 231 | 
            -
                   | 
| 232 | 
            -
                   | 
| 233 | 
            -
             | 
| 234 | 
            -
             | 
| 235 | 
            -
             | 
| 236 | 
            -
             | 
| 237 | 
            -
             | 
| 238 | 
            -
             | 
| 239 | 
            -
             | 
| 240 | 
            -
             | 
| 241 | 
            -
             | 
| 242 | 
            -
                     | 
| 243 | 
            -
                    res =  | 
| 296 | 
            +
                # gets 100 messages prior to id
         | 
| 297 | 
            +
                def more_messages(message_id, limit=100)
         | 
| 298 | 
            +
                  log "more_messages: message_id #{message_id}"
         | 
| 299 | 
            +
                  message_id = message_id.to_i
         | 
| 300 | 
            +
                  if @all_search 
         | 
| 301 | 
            +
                    x = [(message_id - limit), 0].max
         | 
| 302 | 
            +
                    y = [message_id - 1, 0].max
         | 
| 303 | 
            +
                    res = fetch_envelopes((x..y))
         | 
| 304 | 
            +
                    add_more_message_line(res, x)
         | 
| 305 | 
            +
                  else
         | 
| 306 | 
            +
                    # filter search query
         | 
| 307 | 
            +
                    log "@start_index #@start_index"
         | 
| 308 | 
            +
                    x = [(@start_index - limit), 0].max
         | 
| 309 | 
            +
                    y = [@start_index - 1, 0].max
         | 
| 310 | 
            +
                    @start_index = x
         | 
| 311 | 
            +
                    res = fetch_envelopes(@ids[x..y]) 
         | 
| 312 | 
            +
                    add_more_message_line(res, @ids[x])
         | 
| 313 | 
            +
                  end
         | 
| 314 | 
            +
                end
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                def add_more_message_line(res, start_id)
         | 
| 317 | 
            +
                  log "add_more_message_line for start_id #{start_id}"
         | 
| 318 | 
            +
                  if @all_search
         | 
| 319 | 
            +
                    return res if start_id.nil?
         | 
| 320 | 
            +
                    if start_id <= 1
         | 
| 321 | 
            +
                      return res
         | 
| 322 | 
            +
                    end
         | 
| 323 | 
            +
                    remaining = start_id - 1
         | 
| 324 | 
            +
                  else # filter search
         | 
| 325 | 
            +
                    remaining = @ids.index(start_id) - 1
         | 
| 326 | 
            +
                  end
         | 
| 327 | 
            +
                  if remaining < 1
         | 
| 328 | 
            +
                    log "none remaining"
         | 
| 329 | 
            +
                    return res
         | 
| 244 330 | 
             
                  end
         | 
| 245 | 
            -
                   | 
| 331 | 
            +
                  log "remaining messages: #{remaining}"
         | 
| 332 | 
            +
                  "> Load #{[100, remaining].min} more messages. #{remaining} remaining.\n" + res
         | 
| 246 333 | 
             
                end
         | 
| 247 334 |  | 
| 248 | 
            -
                def show_message( | 
| 249 | 
            -
                   | 
| 250 | 
            -
                  if forwarded
         | 
| 251 | 
            -
                    return @current_message.split(/\n-{20,}\n/, 2)[1]
         | 
| 252 | 
            -
                  end
         | 
| 335 | 
            +
                def show_message(index, raw=false)
         | 
| 336 | 
            +
                  log "showing message at #{index}" 
         | 
| 253 337 | 
             
                  return @current_mail.to_s if raw 
         | 
| 254 | 
            -
                   | 
| 255 | 
            -
                   | 
| 338 | 
            +
                  index = index.to_i
         | 
| 339 | 
            +
                  return @current_message if index == @current_message_index 
         | 
| 340 | 
            +
                  envelope_data = @message_list[index]
         | 
| 341 | 
            +
                  uid = envelope_data[:uid] 
         | 
| 342 | 
            +
                  log "fetching uid #{uid}"
         | 
| 256 343 | 
             
                  fetch_data = reconnect_if_necessary do 
         | 
| 257 344 | 
             
                    @imap.uid_fetch(uid, ["FLAGS", "RFC822", "RFC822.SIZE"])[0]
         | 
| 258 345 | 
             
                  end
         | 
| 259 346 | 
             
                  res = fetch_data.attr["RFC822"]
         | 
| 260 347 | 
             
                  mail = Mail.new(res) 
         | 
| 261 | 
            -
                  @ | 
| 348 | 
            +
                  @current_message_index = index
         | 
| 262 349 | 
             
                  @current_mail = mail # used later to show raw message or extract attachments if any
         | 
| 263 350 | 
             
                  log "saving current mail with parts: #{@current_mail.parts.inspect}"
         | 
| 264 351 | 
             
                  formatter = Vmail::MessageFormatter.new(mail)
         | 
| 265 352 | 
             
                  out = formatter.process_body 
         | 
| 266 353 | 
             
                  size = fetch_data.attr["RFC822.SIZE"]
         | 
| 267 354 | 
             
                  @current_message = <<-EOF
         | 
| 268 | 
            -
            #{@mailbox} #{ | 
| 355 | 
            +
            #{@mailbox} #{index} #{number_to_human_size size} #{format_parts_info(formatter.list_parts)}
         | 
| 269 356 | 
             
            ---------------------------------------
         | 
| 270 357 | 
             
            #{format_headers(formatter.extract_headers)}
         | 
| 271 358 |  | 
| @@ -283,36 +370,66 @@ EOF | |
| 283 370 | 
             
                  end
         | 
| 284 371 | 
             
                end
         | 
| 285 372 |  | 
| 286 | 
            -
                #  | 
| 373 | 
            +
                # id_set is a string comming from the vim client
         | 
| 287 374 | 
             
                # action is -FLAGS or +FLAGS
         | 
| 288 | 
            -
                def flag( | 
| 289 | 
            -
                   | 
| 290 | 
            -
             | 
| 291 | 
            -
                  end
         | 
| 292 | 
            -
                  # #<struct Net::IMAP::FetchData seqno=17423, attr={"FLAGS"=>[:Seen, "Flagged"], "UID"=>83113}>
         | 
| 293 | 
            -
                  log "flag #{uid_set} #{flg} #{action}"
         | 
| 375 | 
            +
                def flag(index_range, action, flg)
         | 
| 376 | 
            +
                  uid_set = uids_from_index_range(index_range)
         | 
| 377 | 
            +
                  log "flag #{uid_set.inspect} #{flg} #{action}"
         | 
| 294 378 | 
             
                  if flg == 'Deleted'
         | 
| 379 | 
            +
                    log "Deleting index_range: #{index_range.inspect}; uid_set: #{uid_set.inspect}"
         | 
| 295 380 | 
             
                    # for delete, do in a separate thread because deletions are slow
         | 
| 296 381 | 
             
                    Thread.new do 
         | 
| 297 382 | 
             
                      unless @mailbox == '[Gmail]/Trash'
         | 
| 298 383 | 
             
                        @imap.uid_copy(uid_set, "[Gmail]/Trash")
         | 
| 299 384 | 
             
                      end
         | 
| 300 385 | 
             
                      res = @imap.uid_store(uid_set, action, [flg.to_sym])
         | 
| 386 | 
            +
                      remove_uid_set_from_cached_lists(uid_set)
         | 
| 387 | 
            +
                      reload_mailbox
         | 
| 301 388 | 
             
                    end
         | 
| 302 | 
            -
                    uid_set.each { |uid| @all_uids.delete(uid) }
         | 
| 303 389 | 
             
                  elsif flg == '[Gmail]/Spam'
         | 
| 304 | 
            -
                     | 
| 305 | 
            -
                     | 
| 306 | 
            -
             | 
| 390 | 
            +
                    log "Marking as spam index_range: #{index_range.inspect}; uid_set: #{uid_set.inspect}"
         | 
| 391 | 
            +
                    Thread.new do 
         | 
| 392 | 
            +
                      @imap.uid_copy(uid_set, "[Gmail]/Spam")
         | 
| 393 | 
            +
                      res = @imap.uid_store(uid_set, action, [:Deleted])
         | 
| 394 | 
            +
                      remove_uid_set_from_cached_lists(uid_set)
         | 
| 395 | 
            +
                      reload_mailbox
         | 
| 396 | 
            +
                    end
         | 
| 397 | 
            +
                    "#{id} deleted"
         | 
| 307 398 | 
             
                  else
         | 
| 308 | 
            -
                    log "Flagging"
         | 
| 399 | 
            +
                    log "Flagging index_range: #{index_range.inspect}; uid_set: #{uid_set.inspect}"
         | 
| 309 400 | 
             
                    res = @imap.uid_store(uid_set, action, [flg.to_sym])
         | 
| 310 | 
            -
                     | 
| 311 | 
            -
                     | 
| 312 | 
            -
                  end
         | 
| 401 | 
            +
                    log res.inspect
         | 
| 402 | 
            +
                    fetch_envelopes(uid_set, true).tap {|x| log x}
         | 
| 403 | 
            +
                  end
         | 
| 404 | 
            +
                end
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                def uids_from_index_range(index_range_as_string)
         | 
| 407 | 
            +
                  raise "expecting String" unless index_range_as_string.is_a?(String)
         | 
| 408 | 
            +
                  raise "expecting a range as string" unless index_range_as_string =~ /^\d+\.\.\d+$/ 
         | 
| 409 | 
            +
                  log "converting index_range #{index_range_as_string} to uids"
         | 
| 410 | 
            +
                  uids = @message_list[eval(index_range_as_string)].map {|row| row[:uid]}
         | 
| 411 | 
            +
                  log "converted index_range #{index_range_as_string} to uids #{uids.inspect}"
         | 
| 412 | 
            +
                  uids
         | 
| 413 | 
            +
                end
         | 
| 414 | 
            +
             | 
| 415 | 
            +
                def remove_uid_set_from_cached_lists(uid_set)
         | 
| 416 | 
            +
                  # delete from cached @ids and @message_list
         | 
| 417 | 
            +
                  uid_set.each {|uid| 
         | 
| 418 | 
            +
                    @message_list.
         | 
| 419 | 
            +
                      select {|row| row[:uid] == uid}.
         | 
| 420 | 
            +
                      each {|row| 
         | 
| 421 | 
            +
                        seqno = row[:seqno]
         | 
| 422 | 
            +
                        log "deleting seqno #{seqno} from @ids"
         | 
| 423 | 
            +
                        @ids.delete seqno
         | 
| 424 | 
            +
                        log "deleting msg uid #{row[:uid]} from @message_list"
         | 
| 425 | 
            +
                        @message_list.delete(row)
         | 
| 426 | 
            +
                      }
         | 
| 427 | 
            +
                  }
         | 
| 428 | 
            +
             | 
| 313 429 | 
             
                end
         | 
| 314 430 |  | 
| 315 | 
            -
                def move_to( | 
| 431 | 
            +
                def move_to(id_set, mailbox)
         | 
| 432 | 
            +
                  log "move #{id_set.inspect} to #{mailbox}"
         | 
| 316 433 | 
             
                  if mailbox == 'all'
         | 
| 317 434 | 
             
                    log "archiving messages"
         | 
| 318 435 | 
             
                  end
         | 
| @@ -320,24 +437,28 @@ EOF | |
| 320 437 | 
             
                    mailbox = MailboxAliases[mailbox]
         | 
| 321 438 | 
             
                  end
         | 
| 322 439 | 
             
                  create_if_necessary mailbox
         | 
| 323 | 
            -
                   | 
| 324 | 
            -
             | 
| 440 | 
            +
                  log "getting uids form index range #{id_set}"
         | 
| 441 | 
            +
                  uid_set = uids_from_index_range(id_set)
         | 
| 442 | 
            +
                  log "moving uid_set: #{uid_set.inspect} to #{mailbox}"
         | 
| 443 | 
            +
                  Thread.new do 
         | 
| 444 | 
            +
                    log @imap.uid_copy(uid_set, mailbox)
         | 
| 445 | 
            +
                    log @imap.uid_store(uid_set, '+FLAGS', [:Deleted])
         | 
| 446 | 
            +
                    reload_mailbox
         | 
| 447 | 
            +
                    log "moved uid_set #{uid_set.inspect} to #{mailbox}"
         | 
| 325 448 | 
             
                  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])
         | 
| 329 449 | 
             
                end
         | 
| 330 450 |  | 
| 331 | 
            -
                def copy_to( | 
| 451 | 
            +
                def copy_to(id_set, mailbox)
         | 
| 332 452 | 
             
                  if MailboxAliases[mailbox]
         | 
| 333 453 | 
             
                    mailbox = MailboxAliases[mailbox]
         | 
| 334 454 | 
             
                  end
         | 
| 335 455 | 
             
                  create_if_necessary mailbox
         | 
| 336 | 
            -
                   | 
| 337 | 
            -
                   | 
| 338 | 
            -
             | 
| 456 | 
            +
                  uid_set = uids_from_index_range(id_set)
         | 
| 457 | 
            +
                  log "copying #{uid_set.inspect} to #{mailbox}"
         | 
| 458 | 
            +
                  Thread.new do 
         | 
| 459 | 
            +
                    log @imap.uid_copy(uid_set, mailbox)
         | 
| 460 | 
            +
                    log "copied uid_set #{uid_set.inspect} to #{mailbox}"
         | 
| 339 461 | 
             
                  end
         | 
| 340 | 
            -
                  log @imap.uid_copy(uid_set, mailbox)
         | 
| 341 462 | 
             
                end
         | 
| 342 463 |  | 
| 343 464 | 
             
                def create_if_necessary(mailbox)
         | 
| @@ -350,18 +471,18 @@ EOF | |
| 350 471 | 
             
                  end
         | 
| 351 472 | 
             
                end
         | 
| 352 473 |  | 
| 353 | 
            -
                def append_to_file(file,  | 
| 354 | 
            -
                   | 
| 355 | 
            -
             | 
| 356 | 
            -
                   | 
| 357 | 
            -
                   | 
| 358 | 
            -
             | 
| 359 | 
            -
                    message = show_message(uid)
         | 
| 474 | 
            +
                def append_to_file(file, index_range_as_string)
         | 
| 475 | 
            +
                  raise "expecting a range as string" unless index_range_as_string =~ /^\d+\.\.\d+$/ 
         | 
| 476 | 
            +
                  index_range = eval(index_range_as_string)
         | 
| 477 | 
            +
                  log "append to file range #{index_range.inspect} to file: #{file}"
         | 
| 478 | 
            +
                  index_range.each do |idx|
         | 
| 479 | 
            +
                    message = show_message(idx)
         | 
| 360 480 | 
             
                    divider = "#{'=' * 39}\n"
         | 
| 361 481 | 
             
                    File.open(file, 'a') {|f| f.puts(divider + message + "\n\n")}
         | 
| 362 | 
            -
                     | 
| 482 | 
            +
                    subject = (message[/^subject:(.*)/,1] || '').strip
         | 
| 483 | 
            +
                    log "appended message '#{subject}'"
         | 
| 363 484 | 
             
                  end
         | 
| 364 | 
            -
                  "printed #{ | 
| 485 | 
            +
                  "printed #{index_range.to_a.size} message#{index_range.to_a.size == 1 ? '' : 's'} to #{file.strip}"
         | 
| 365 486 | 
             
                end
         | 
| 366 487 |  | 
| 367 488 |  | 
| @@ -384,41 +505,22 @@ EOF | |
| 384 505 | 
             
                  lines.join("\n")
         | 
| 385 506 | 
             
                end
         | 
| 386 507 |  | 
| 387 | 
            -
                def reply_template( | 
| 388 | 
            -
                  log "sending reply template | 
| 389 | 
            -
                   | 
| 390 | 
            -
                   | 
| 391 | 
            -
                   | 
| 392 | 
            -
                  cc = [envelope.to, envelope.cc]
         | 
| 393 | 
            -
                  cc = cc.flatten.compact.
         | 
| 394 | 
            -
                    select {|x| @username !~ /#{x.mailbox}@#{x.host}/}.
         | 
| 395 | 
            -
                    map {|x| address_to_string(x)}.join(", ")
         | 
| 396 | 
            -
                  mail = Mail.new fetch_data.attr['RFC822']
         | 
| 397 | 
            -
                  formatter = Vmail::MessageFormatter.new(mail)
         | 
| 398 | 
            -
                  headers = formatter.extract_headers
         | 
| 399 | 
            -
                  subject = headers['subject']
         | 
| 400 | 
            -
                  if subject !~ /Re: /
         | 
| 401 | 
            -
                    subject = "Re: #{subject}"
         | 
| 402 | 
            -
                  end
         | 
| 403 | 
            -
                  cc = replyall ? cc : nil
         | 
| 404 | 
            -
                  date = headers['date'].is_a?(String) ? Time.parse(headers['date']) : headers['date']
         | 
| 405 | 
            -
                  quote_header = "On #{date.strftime('%a, %b %d, %Y at %I:%M %p')}, #{address_to_string(envelope.from[0])} wrote:\n\n"
         | 
| 406 | 
            -
                  body = quote_header + formatter.process_body.gsub(/^(?=>)/, ">").gsub(/^(?!>)/, "> ")
         | 
| 407 | 
            -
                  reply_headers = { 'from' => "#@name <#@username>", 'to' => recipient, 'cc' => cc, 'subject' => subject}
         | 
| 508 | 
            +
                def reply_template(replyall=false)
         | 
| 509 | 
            +
                  log "sending reply template"
         | 
| 510 | 
            +
                  # user reply_template class
         | 
| 511 | 
            +
                  reply_headers = Vmail::ReplyTemplate.new(@current_mail, @username, @name, replyall).reply_headers
         | 
| 512 | 
            +
                  body = reply_headers.delete(:body)
         | 
| 408 513 | 
             
                  format_headers(reply_headers) + "\n\n\n" + body + signature
         | 
| 409 514 | 
             
                end
         | 
| 410 515 |  | 
| 411 | 
            -
                def address_to_string(x)
         | 
| 412 | 
            -
                  x.name ? "#{x.name} <#{x.mailbox}@#{x.host}>" : "#{x.mailbox}@#{x.host}"
         | 
| 413 | 
            -
                end
         | 
| 414 516 |  | 
| 415 517 | 
             
                def signature
         | 
| 416 518 | 
             
                  return '' unless @signature
         | 
| 417 519 | 
             
                  "\n\n#@signature"
         | 
| 418 520 | 
             
                end
         | 
| 419 521 |  | 
| 420 | 
            -
                def forward_template | 
| 421 | 
            -
                  original_body =  | 
| 522 | 
            +
                def forward_template
         | 
| 523 | 
            +
                  original_body = @current_message.split(/\n-{20,}\n/, 2)[1]
         | 
| 422 524 | 
             
                  new_message_template + 
         | 
| 423 525 | 
             
                    "\n---------- Forwarded message ----------\n" +
         | 
| 424 526 | 
             
                    original_body + signature
         | 
| @@ -427,15 +529,7 @@ EOF | |
| 427 529 | 
             
                def deliver(text)
         | 
| 428 530 | 
             
                  # parse the text. The headers are yaml. The rest is text body.
         | 
| 429 531 | 
             
                  require 'net/smtp'
         | 
| 430 | 
            -
                   | 
| 431 | 
            -
                    # this is just to prime the IMAP connection
         | 
| 432 | 
            -
                    # It's necessary for some reason.
         | 
| 433 | 
            -
                    log "priming connection for delivering"
         | 
| 434 | 
            -
                    res = @imap.uid_fetch(@all_uids[-1], ["ENVELOPE"])
         | 
| 435 | 
            -
                    if res.nil?
         | 
| 436 | 
            -
                      raise IOError, "IMAP connection seems broken"
         | 
| 437 | 
            -
                    end
         | 
| 438 | 
            -
                  end 
         | 
| 532 | 
            +
                  prime_connection
         | 
| 439 533 | 
             
                  mail = new_mail_from_input(text)
         | 
| 440 534 | 
             
                  mail.delivery_method(*smtp_settings)
         | 
| 441 535 | 
             
                  log mail.deliver!
         | 
| @@ -518,8 +612,8 @@ EOF | |
| 518 612 | 
             
                  "saved:\n" + saved.map {|x| "- #{x}"}.join("\n")
         | 
| 519 613 | 
             
                end
         | 
| 520 614 |  | 
| 521 | 
            -
                def open_html_part | 
| 522 | 
            -
                  log "open_html_part | 
| 615 | 
            +
                def open_html_part
         | 
| 616 | 
            +
                  log "open_html_part"
         | 
| 523 617 | 
             
                  log @current_mail.parts.inspect
         | 
| 524 618 | 
             
                  multipart = @current_mail.parts.detect {|part| part.multipart?}
         | 
| 525 619 | 
             
                  html_part = if multipart 
         | 
| @@ -571,6 +665,9 @@ EOF | |
| 571 665 | 
             
                  log(revive_connection)
         | 
| 572 666 | 
             
                  # try just once
         | 
| 573 667 | 
             
                  block.call
         | 
| 668 | 
            +
                rescue
         | 
| 669 | 
            +
                  log "error: #{$!}"
         | 
| 670 | 
            +
                  raise
         | 
| 574 671 | 
             
                end
         | 
| 575 672 |  | 
| 576 673 | 
             
                def self.start(config)
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            require 'vmail/message_formatter'
         | 
| 2 | 
            +
            require 'mail'
         | 
| 3 | 
            +
            require 'time'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Vmail
         | 
| 6 | 
            +
              class ReplyTemplate
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(mail, username, name, replyall)
         | 
| 9 | 
            +
                  @username, @name, @replyall = username, name, replyall
         | 
| 10 | 
            +
                  @mail = Mail.new(mail)
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def reply_headers
         | 
| 14 | 
            +
                  formatter = Vmail::MessageFormatter.new(@mail)
         | 
| 15 | 
            +
                  headers = formatter.extract_headers
         | 
| 16 | 
            +
                  subject = headers['subject']
         | 
| 17 | 
            +
                  if subject !~ /Re: /
         | 
| 18 | 
            +
                    subject = "Re: #{subject}"
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                  date = headers['date'].is_a?(String) ? Time.parse(headers['date']) : headers['date']
         | 
| 21 | 
            +
                  quote_header = "On #{date.strftime('%a, %b %d, %Y at %I:%M %p')}, #{sender} wrote:\n\n"
         | 
| 22 | 
            +
                  body = quote_header + formatter.process_body.gsub(/^(?=>)/, ">").gsub(/^(?!>)/, "> ")
         | 
| 23 | 
            +
                  {'from' => "#@name <#@username>", 'to' => primary_recipient, 'cc' => cc, 'subject' => subject, :body => body}
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def primary_recipient
         | 
| 27 | 
            +
                  from = @mail.header['from']
         | 
| 28 | 
            +
                  reply_to = @mail.header['reply-to']
         | 
| 29 | 
            +
                  [ reply_to, from ].flatten.compact.map(&:to_s)[0]
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def cc
         | 
| 33 | 
            +
                  return nil unless @replyall
         | 
| 34 | 
            +
                  cc = @mail.header['to'].value.split(/,\s*/) + @mail.header['cc'].value.split(/,\s*/) 
         | 
| 35 | 
            +
                  cc = cc.flatten.compact.
         | 
| 36 | 
            +
                    select {|x| 
         | 
| 37 | 
            +
                      x.to_s[/<([^>]+)>/, 1] !~ /#{@username}/ && x.to_s[/^[^<]+/, 1] !~ /#{@name}/
         | 
| 38 | 
            +
                      }.join(', ')
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def sender
         | 
| 42 | 
            +
                  @mail.header['from'].value
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                # deprecated
         | 
| 46 | 
            +
                def address_to_string(x)
         | 
| 47 | 
            +
                  x.name ? "#{x.name} <#{x.mailbox}@#{x.host}>" : "#{x.mailbox}@#{x.host}"
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
            end
         | 
    
        data/lib/vmail/version.rb
    CHANGED
    
    
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            Return-Path: <chappy1@gmail.com>
         | 
| 2 | 
            +
            Received: by 10.213.2.204 with SMTP id 12cs233347ebk; Sun, 12 Dec 2010 10:13:13 -0800
         | 
| 3 | 
            +
            Received: by 10.91.51.19 with SMTP id d19mr3869811agk.183.1292177592502; Sun, 12 Dec 2010 10:13:12 -0800
         | 
| 4 | 
            +
            Received: from mail-yx0-f171.google.com (mail-yx0-f171.google.com [209.85.213.171]) by mx.google.com with ESMTP id d7si4681466and.167.2010.12.12.10.13.10; Sun, 12 Dec 2010 10:13:11 -0800
         | 
| 5 | 
            +
            Received: by mail-yx0-f171.google.com with SMTP id 11so3634988yxi.2 for
         | 
| 6 | 
            +
             <multiple recipients>; Sun, 12 Dec 2010 10:13:10 -0800 (PST)
         | 
| 7 | 
            +
            Received: by 10.90.24.10 with SMTP id 10mr3890562agx.179.1292177590623; Sun, 12 Dec 2010 10:13:10 -0800
         | 
| 8 | 
            +
            Received: from [26.144.14.48] (m482436d0.tmodns.net [208.54.36.72]) by mx.google.com with ESMTPS id c34sm4564739anc.30.2010.12.12.10.13.08 (version=TLSv1/SSLv3 cipher=RC4-MD5); Sun, 12 Dec 2010 10:13:09 -0800
         | 
| 9 | 
            +
            Date: Sun, 12 Dec 2010 13:13:01 -0500
         | 
| 10 | 
            +
            From: Chappy Youn <chappy1@gmail.com>
         | 
| 11 | 
            +
            To: Daniel Choi <dhchoi@gmail.com>, 
         | 
| 12 | 
            +
             Draculette Ko <violinist.ko@gmail.com>, 
         | 
| 13 | 
            +
             Cookiemonster Youn <cookiemonster@gmail.com>
         | 
| 14 | 
            +
            Cc: Racoon <raycoon@gmail.com>
         | 
| 15 | 
            +
            Message-ID: <73734601-F016-4F3B-A0C8-0FF48011292F@gmail.com>
         | 
| 16 | 
            +
            Subject: Holiday potluck at Ray's
         | 
| 17 | 
            +
            Mime-Version: 1.0
         | 
| 18 | 
            +
            Content-Type: text/plain;
         | 
| 19 | 
            +
             charset=us-ascii;
         | 
| 20 | 
            +
             delsp=yes;
         | 
| 21 | 
            +
             format=flowed
         | 
| 22 | 
            +
            Content-Transfer-Encoding: 7bit
         | 
| 23 | 
            +
            Delivered-To: dhchoi@gmail.com
         | 
| 24 | 
            +
            Received-SPF: pass (google.com: domain of chappy1@gmail.com designates
         | 
| 25 | 
            +
             209.85.213.171 as permitted sender) client-ip=209.85.213.171;
         | 
| 26 | 
            +
            Authentication-Results: mx.google.com; spf=pass (google.com: domain of
         | 
| 27 | 
            +
             chappy1@gmail.com designates 209.85.213.171 as permitted sender)
         | 
| 28 | 
            +
             smtp.mail=chappy1@gmail.com; dkim=pass (test mode) header.i=@gmail.com
         | 
| 29 | 
            +
            DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma;
         | 
| 30 | 
            +
             h=domainkey-signature:received:received:message-id:from:to
         | 
| 31 | 
            +
             :content-type:content-transfer-encoding:x-mailer:mime-version
         | 
| 32 | 
            +
             :subject:date:cc; bh=2E6xsnWGlAai1MjVchauqPtC2J0dmGnscU+hCRvcScY=;
         | 
| 33 | 
            +
             b=joH+NKL712kaFO0ThbHEF/7q9qSf1O6Xi9u2TBCp3uQl0aEVwuqGFHcg+id0/Am5qM
         | 
| 34 | 
            +
             IoxNBItJn9zMUgmzzZcJSl5/LaxKwrj8NOn/eYF8fkKRecCbNUoCYV4Z0T7BkuPwpkWA
         | 
| 35 | 
            +
             PYPun1PZT8ArAM2aXoMzApEnzOCvf/ZkM78Q4=
         | 
| 36 | 
            +
            DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma;
         | 
| 37 | 
            +
             h=message-id:from:to:content-type:content-transfer-encoding:x-mailer
         | 
| 38 | 
            +
             :mime-version:subject:date:cc;
         | 
| 39 | 
            +
             b=fCuCk6O03S/wqKttwtK0ePIAy7MFja+qOyIkt9X/ToJg8MiPoHh6UH5o0UaAlAEyE7
         | 
| 40 | 
            +
             7C8iRyxjV4EC3vfQhzHUzZHFuXAOtPOF6ZSidHKbXVad3Wvv0zie9dV3pFXCbg0fyC8M
         | 
| 41 | 
            +
             c1nHTFM+So9DDIulICizW196SVSh65Ds+NVK4=
         | 
| 42 | 
            +
            X-Mailer: iPhone Mail (7C144)
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            Guys,
         | 
| 45 | 
            +
            Tonight we will have a potluck at Ray's at 7. Pls bring food for 1.5  
         | 
| 46 | 
            +
            ppl.
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            Ray will provide wine and dessert.
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            Also, we will be having a poor man's Yankee swap. Pls bring something  
         | 
| 51 | 
            +
            gift wrapped from home. Nothing fancy, but something halfway decent or  
         | 
| 52 | 
            +
            funny.
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            El, make sure it's worth more than 50 cents.
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            Chappy
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            Sent from my iPhone
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
            require 'vmail/reply_template'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Vmail::ReplyTemplate do
         | 
| 5 | 
            +
              before do
         | 
| 6 | 
            +
                @raw = read_fixture('reply_all.eml')
         | 
| 7 | 
            +
                @mail = Mail.new(@raw)
         | 
| 8 | 
            +
                @rt = Vmail::ReplyTemplate.new(@mail, 'dhchoi@gmail.com', 'Daniel Choi', true)
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def test_detect_primary_recipient
         | 
| 12 | 
            +
                assert_equal "Chappy Youn <chappy1@gmail.com>", @rt.primary_recipient
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def test_detect_cc
         | 
| 16 | 
            +
                expected = "Draculette Ko <violinist.ko@gmail.com>, Cookiemonster Youn <cookiemonster@gmail.com>, Racoon <raycoon@gmail.com>"
         | 
| 17 | 
            +
                assert_equal expected, @rt.cc
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
              
         | 
| 20 | 
            +
              def test_sender
         | 
| 21 | 
            +
                assert_equal "Chappy Youn <chappy1@gmail.com>", @rt.sender
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def test_template
         | 
| 25 | 
            +
                expected = {"from"=>"Daniel Choi <dhchoi@gmail.com>", "to"=>"Chappy Youn <chappy1@gmail.com>", "cc"=>"Draculette Ko <violinist.ko@gmail.com>, Cookiemonster Youn <cookiemonster@gmail.com>, Racoon <raycoon@gmail.com>", "subject"=>"Re: Holiday potluck at Ray's", :body=>"On Sun, Dec 12, 2010 at 01:13 PM, Chappy Youn <chappy1@gmail.com> wrote:\n\n> Guys,\n> Tonight we will have a potluck at Ray's at 7. Pls bring food for 1.5  \n> ppl.\n> \n> Ray will provide wine and dessert.\n> \n> Also, we will be having a poor man's Yankee swap. Pls bring something  \n> gift wrapped from home. Nothing fancy, but something halfway decent or  \n> funny.\n> \n> El, make sure it's worth more than 50 cents.\n> \n> Chappy\n> \n> Sent from my iPhone"}
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                assert_equal expected,  @rt.reply_headers
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
             | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
    
        data/website/vmail-template.html
    CHANGED
    
    | @@ -23,10 +23,6 @@ | |
| 23 23 |  | 
| 24 24 | 
             
                      <div class="sidebar">
         | 
| 25 25 |  | 
| 26 | 
            -
                        <h4>share this</h4>
         | 
| 27 | 
            -
             | 
| 28 | 
            -
            <span class="st_twitter_large" displayText="Tweet"></span><span class="st_facebook_large" displayText="Facebook"></span><span class="st_ybuzz_large" displayText="Yahoo! Buzz"></span><span class="st_gbuzz_large" displayText="Google Buzz"></span><span class="st_email_large" displayText="Email"></span><span class="st_sharethis_large" displayText="ShareThis"></span>
         | 
| 29 | 
            -
             | 
| 30 26 | 
             
                        <h4>links</h4>
         | 
| 31 27 | 
             
                        <ul>
         | 
| 32 28 | 
             
                          <li><a href="https://github.com/danchoi/vmail">github repo</a></li>
         | 
| @@ -34,6 +30,10 @@ | |
| 34 30 | 
             
                          <li><a href="https://github.com/danchoi/vmail/issues">issue tracker</a></li>
         | 
| 35 31 | 
             
                          <li><a href="http://www.google.com/search?sourceid=chrome&ie=UTF-8&q=vmail+vim#hl=en&tbo=1&tbs=mbl:1,mbl_sv:0&q=link:http://danielchoi.com/software/vmail.html&sa=X&ei=iw0JTYnsPMT_lgfgvMC1AQ&ved=0CAUQ6QcwCg&fp=1&cad=b">web reactions</a></li>
         | 
| 36 32 | 
             
                        </ul>
         | 
| 33 | 
            +
                        <h4>share this</h4>
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            <span class="st_twitter_large" displayText="Tweet"></span><span class="st_facebook_large" displayText="Facebook"></span><span class="st_ybuzz_large" displayText="Yahoo! Buzz"></span><span class="st_gbuzz_large" displayText="Google Buzz"></span><span class="st_email_large" displayText="Email"></span><span class="st_sharethis_large" displayText="ShareThis"></span>
         | 
| 36 | 
            +
             | 
| 37 37 | 
             
                      </div>
         | 
| 38 38 |  | 
| 39 39 | 
             
                  </div>
         | 
    
        metadata
    CHANGED
    
    | @@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version | |
| 5 5 | 
             
              segments: 
         | 
| 6 6 | 
             
              - 0
         | 
| 7 7 | 
             
              - 4
         | 
| 8 | 
            -
              -  | 
| 9 | 
            -
              version: 0.4. | 
| 8 | 
            +
              - 6
         | 
| 9 | 
            +
              version: 0.4.6
         | 
| 10 10 | 
             
            platform: ruby
         | 
| 11 11 | 
             
            authors: 
         | 
| 12 12 | 
             
            - Daniel Choi
         | 
| @@ -71,6 +71,7 @@ files: | |
| 71 71 | 
             
            - lib/vmail/imap_client.rb
         | 
| 72 72 | 
             
            - lib/vmail/message_formatter.rb
         | 
| 73 73 | 
             
            - lib/vmail/options.rb
         | 
| 74 | 
            +
            - lib/vmail/reply_template.rb
         | 
| 74 75 | 
             
            - lib/vmail/string_ext.rb
         | 
| 75 76 | 
             
            - lib/vmail/version.rb
         | 
| 76 77 | 
             
            - test/base64_test.rb
         | 
| @@ -79,10 +80,12 @@ files: | |
| 79 80 | 
             
            - test/fixtures/google-affiliate.eml
         | 
| 80 81 | 
             
            - test/fixtures/htmlbody.eml
         | 
| 81 82 | 
             
            - test/fixtures/moleskine-html.eml
         | 
| 83 | 
            +
            - test/fixtures/reply_all.eml
         | 
| 82 84 | 
             
            - test/fixtures/rfc_part.eml
         | 
| 83 85 | 
             
            - test/fixtures/textbody-nocontenttype.eml
         | 
| 84 86 | 
             
            - test/fixtures/with-attachments.eml
         | 
| 85 87 | 
             
            - test/message_formatter_test.rb
         | 
| 88 | 
            +
            - test/reply_template_test.rb
         | 
| 86 89 | 
             
            - test/test_helper.rb
         | 
| 87 90 | 
             
            - test/time_format_test.rb
         | 
| 88 91 | 
             
            - vmail.gemspec
         | 
| @@ -158,9 +161,11 @@ test_files: | |
| 158 161 | 
             
            - test/fixtures/google-affiliate.eml
         | 
| 159 162 | 
             
            - test/fixtures/htmlbody.eml
         | 
| 160 163 | 
             
            - test/fixtures/moleskine-html.eml
         | 
| 164 | 
            +
            - test/fixtures/reply_all.eml
         | 
| 161 165 | 
             
            - test/fixtures/rfc_part.eml
         | 
| 162 166 | 
             
            - test/fixtures/textbody-nocontenttype.eml
         | 
| 163 167 | 
             
            - test/fixtures/with-attachments.eml
         | 
| 164 168 | 
             
            - test/message_formatter_test.rb
         | 
| 169 | 
            +
            - test/reply_template_test.rb
         | 
| 165 170 | 
             
            - test/test_helper.rb
         | 
| 166 171 | 
             
            - test/time_format_test.rb
         |