vmail 1.1.9 → 1.2.0
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/TODO +4 -0
- data/lib/vmail.vim +35 -24
- data/lib/vmail/imap_client.rb +64 -177
- data/lib/vmail/version.rb +1 -1
- data/website/stylesheets-vmail/site.css +9 -0
- data/website/vmail-template.html +12 -1
- metadata +4 -4
    
        data/TODO
    CHANGED
    
    | @@ -101,4 +101,8 @@ Sat Dec 25 09:07:28 EST 2010 | |
| 101 101 | 
             
            - change vmail to take a file or files (e.g. globbed) as argument and
         | 
| 102 102 | 
             
              send them. This will let the user enter password on invocation.
         | 
| 103 103 |  | 
| 104 | 
            +
            ------------------------------------------------------------------------
         | 
| 105 | 
            +
            Thu Dec 30 11:28:05 EST 2010
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            1/6 deadline for doing How I Work Screencast
         | 
| 104 108 |  | 
    
        data/lib/vmail.vim
    CHANGED
    
    | @@ -75,6 +75,11 @@ function! s:show_message(stay_in_message_list) | |
| 75 75 | 
             
                call s:more_messages()
         | 
| 76 76 | 
             
                return
         | 
| 77 77 | 
             
              endif
         | 
| 78 | 
            +
              let s:uid = matchstr(line, '\d\+$')
         | 
| 79 | 
            +
              if s:uid == ""
         | 
| 80 | 
            +
                return
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
              " mark as read
         | 
| 78 83 | 
             
              let newline = substitute(line, '^\V*+', '* ', '')
         | 
| 79 84 | 
             
              let newline = substitute(newline, '^\V+ ', '  ', '')
         | 
| 80 85 | 
             
              setlocal modifiable
         | 
| @@ -85,13 +90,8 @@ function! s:show_message(stay_in_message_list) | |
| 85 90 | 
             
              " moving up when the next echo statement executes:
         | 
| 86 91 | 
             
              " call feedkeys(":\<cr>") 
         | 
| 87 92 | 
             
              " redraw
         | 
| 88 | 
            -
               | 
| 89 | 
            -
               | 
| 90 | 
            -
              if s:current_message_index < 0
         | 
| 91 | 
            -
                return
         | 
| 92 | 
            -
              endif
         | 
| 93 | 
            -
              let command = s:show_message_command . s:current_message_index
         | 
| 94 | 
            -
              echom "Loading message. Please wait..."
         | 
| 93 | 
            +
              let command = s:show_message_command . s:uid
         | 
| 94 | 
            +
              echom "Loading message ". s:uid .". Please wait..."
         | 
| 95 95 | 
             
              redrawstatus
         | 
| 96 96 | 
             
              let res = system(command)
         | 
| 97 97 | 
             
              call s:focus_message_window()
         | 
| @@ -243,15 +243,15 @@ function! s:update() | |
| 243 243 | 
             
            endfunction
         | 
| 244 244 |  | 
| 245 245 | 
             
            function! s:toggle_star() range
         | 
| 246 | 
            -
              let uid_set = (a:firstline  | 
| 247 | 
            -
              let nummsgs = ( | 
| 246 | 
            +
              let uid_set = s:collect_uids(a:firstline, a:lastline)
         | 
| 247 | 
            +
              let nummsgs = len(uid_set)
         | 
| 248 248 | 
             
              let flag_symbol = "^*"
         | 
| 249 249 | 
             
              " check if starred already
         | 
| 250 250 | 
             
              let action = " +FLAGS"
         | 
| 251 251 | 
             
              if (match(getline(a:firstline), flag_symbol) != -1)
         | 
| 252 252 | 
             
                let action = " -FLAGS"
         | 
| 253 253 | 
             
              endif
         | 
| 254 | 
            -
              let command = s:flag_command . uid_set . action . " Flagged" 
         | 
| 254 | 
            +
              let command = s:flag_command . join(uid_set, ',') . action . " Flagged" 
         | 
| 255 255 | 
             
              if nummsgs == 1
         | 
| 256 256 | 
             
                echom "toggling flag on message" 
         | 
| 257 257 | 
             
              else
         | 
| @@ -288,13 +288,13 @@ endfunction | |
| 288 288 |  | 
| 289 289 | 
             
            " flag can be Deleted or [Gmail]/Spam
         | 
| 290 290 | 
             
            func! s:delete_messages(flag) range
         | 
| 291 | 
            -
              let uid_set = (a:firstline  | 
| 292 | 
            -
              let  | 
| 293 | 
            -
              let  | 
| 291 | 
            +
              let uid_set = s:collect_uids(a:firstline, a:lastline)
         | 
| 292 | 
            +
              let nummsgs = len(uid_set)
         | 
| 293 | 
            +
              let command = s:flag_command . join(uid_set, ',') . " +FLAGS " . a:flag
         | 
| 294 294 | 
             
              if nummsgs == 1
         | 
| 295 295 | 
             
                echom "deleting message" 
         | 
| 296 296 | 
             
              else
         | 
| 297 | 
            -
                echom "deleting " .  | 
| 297 | 
            +
                echom "deleting " . nummsgs . " messages"
         | 
| 298 298 | 
             
              endif
         | 
| 299 299 | 
             
              let res = system(command)
         | 
| 300 300 | 
             
              setlocal modifiable
         | 
| @@ -311,9 +311,9 @@ func! s:delete_messages(flag) range | |
| 311 311 | 
             
            endfunc
         | 
| 312 312 |  | 
| 313 313 | 
             
            func! s:archive_messages() range
         | 
| 314 | 
            -
              let uid_set = (a:firstline  | 
| 315 | 
            -
              let  | 
| 316 | 
            -
              let  | 
| 314 | 
            +
              let uid_set = s:collect_uids(a:firstline, a:lastline)
         | 
| 315 | 
            +
              let nummsgs = len(uid_set)
         | 
| 316 | 
            +
              let command = s:move_to_command . join(uid_set, ',') . ' ' . "all"
         | 
| 317 317 | 
             
              echo "archiving message" . (nummsgs == 1 ? '' : 's')
         | 
| 318 318 | 
             
              let res = system(command)
         | 
| 319 319 | 
             
              setlocal modifiable
         | 
| @@ -331,15 +331,15 @@ endfunc | |
| 331 331 |  | 
| 332 332 | 
             
            " append text bodies of a set of messages to a file
         | 
| 333 333 | 
             
            func! s:append_messages_to_file() range
         | 
| 334 | 
            -
              let uid_set = (a:firstline  | 
| 335 | 
            -
              let nummsgs = ( | 
| 334 | 
            +
              let uid_set = s:collect_uids(a:firstline, a:lastline)
         | 
| 335 | 
            +
              let nummsgs = len(uid_set)
         | 
| 336 336 | 
             
              let append_file = input("print messages to file: ", s:append_file)
         | 
| 337 337 | 
             
              if append_file == ''
         | 
| 338 338 | 
             
                echom "canceled"
         | 
| 339 339 | 
             
                return
         | 
| 340 340 | 
             
              endif
         | 
| 341 341 | 
             
              let s:append_file = append_file
         | 
| 342 | 
            -
              let command = s:append_to_file_command . s:append_file . ' ' . uid_set 
         | 
| 342 | 
            +
              let command = s:append_to_file_command . s:append_file . ' ' . join(uid_set, ',')
         | 
| 343 343 | 
             
              echo "appending " . nummsgs . " message" . (nummsgs == 1 ? '' : 's') . " to " . s:append_file . ". please wait..."
         | 
| 344 344 | 
             
              let res = system(command)
         | 
| 345 345 | 
             
              echo res
         | 
| @@ -350,8 +350,9 @@ endfunc | |
| 350 350 | 
             
            " move to another mailbox
         | 
| 351 351 | 
             
            function! s:move_to_mailbox(copy) range
         | 
| 352 352 | 
             
              let s:copy_to_mailbox = a:copy
         | 
| 353 | 
            -
              let  | 
| 354 | 
            -
              let s:nummsgs = ( | 
| 353 | 
            +
              let uid_set = s:collect_uids(a:firstline, a:lastline)
         | 
| 354 | 
            +
              let s:nummsgs = len(uid_set)
         | 
| 355 | 
            +
              let s:uid_set = join(uid_set, ',')
         | 
| 355 356 | 
             
              " now prompt use to select mailbox
         | 
| 356 357 | 
             
              if !exists("s:mailboxes")
         | 
| 357 358 | 
             
                call s:get_mailbox_list()
         | 
| @@ -551,7 +552,8 @@ endfunction | |
| 551 552 |  | 
| 552 553 | 
             
            function! s:more_messages()
         | 
| 553 554 | 
             
              let line = getline(line('.'))
         | 
| 554 | 
            -
              let  | 
| 555 | 
            +
              let seqno = get(split(matchstr(line, '\d\+:\d\+$'), ':'), 0)
         | 
| 556 | 
            +
              let command = s:more_messages_command . seqno
         | 
| 555 557 | 
             
              echo "fetching more messages. please wait..."
         | 
| 556 558 | 
             
              let res = system(command)
         | 
| 557 559 | 
             
              setlocal modifiable
         | 
| @@ -746,7 +748,16 @@ endfunc | |
| 746 748 | 
             
            " -------------------------------------------------------------------------------- 
         | 
| 747 749 | 
             
            " CONVENIENCE FUNCS
         | 
| 748 750 |  | 
| 749 | 
            -
             | 
| 751 | 
            +
            function! s:collect_uids(startline, endline)
         | 
| 752 | 
            +
              let uid_set = []
         | 
| 753 | 
            +
              let lnum = a:startline
         | 
| 754 | 
            +
              while lnum <= a:endline
         | 
| 755 | 
            +
                let uid = matchstr(getline(lnum), '\d\+$')
         | 
| 756 | 
            +
                call add(uid_set, uid)
         | 
| 757 | 
            +
                let lnum += 1
         | 
| 758 | 
            +
              endwhile
         | 
| 759 | 
            +
              return uid_set
         | 
| 760 | 
            +
            endfunc
         | 
| 750 761 |  | 
| 751 762 | 
             
            " -------------------------------------------------------------------------------- 
         | 
| 752 763 | 
             
            " MAPPINGS
         | 
    
        data/lib/vmail/imap_client.rb
    CHANGED
    
    | @@ -20,6 +20,8 @@ module Vmail | |
| 20 20 | 
             
                  'trash' => '[Gmail]/Trash'
         | 
| 21 21 | 
             
                }
         | 
| 22 22 |  | 
| 23 | 
            +
                attr_accessor :max_seqno # of current mailbox
         | 
| 24 | 
            +
             | 
| 23 25 | 
             
                def initialize(config)
         | 
| 24 26 | 
             
                  @username, @password = config['username'], config['password']
         | 
| 25 27 | 
             
                  @name = config['name']
         | 
| @@ -30,11 +32,10 @@ module Vmail | |
| 30 32 | 
             
                  @imap_server = config['server'] || 'imap.gmail.com'
         | 
| 31 33 | 
             
                  @imap_port = config['port'] || 993
         | 
| 32 34 | 
             
                  @current_mail = nil
         | 
| 33 | 
            -
                  @ | 
| 35 | 
            +
                  @current_message_uid = nil
         | 
| 34 36 | 
             
                end
         | 
| 35 37 |  | 
| 36 38 | 
             
                # holds mail objects keyed by [mailbox, uid]
         | 
| 37 | 
            -
                # TODO come up with a way to purge periodically
         | 
| 38 39 | 
             
                def message_cache
         | 
| 39 40 | 
             
                  @message_cache ||= {}
         | 
| 40 41 | 
             
                  size = @message_cache.values.reduce(0) {|sum, x| sum + x[:size]}
         | 
| @@ -45,20 +46,6 @@ module Vmail | |
| 45 46 | 
             
                  @message_cache
         | 
| 46 47 | 
             
                end
         | 
| 47 48 |  | 
| 48 | 
            -
                # keys are [mailbox, limit, query]
         | 
| 49 | 
            -
                def message_list_cache
         | 
| 50 | 
            -
                  @message_list_cache ||= {}
         | 
| 51 | 
            -
                end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                def current_message_list_cache
         | 
| 54 | 
            -
                  # third key is the non id_set/range part of query
         | 
| 55 | 
            -
                  (message_list_cache[[@mailbox, @limit, @query[1..-1], @all_search]] ||= [])
         | 
| 56 | 
            -
                end
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                def current_message_list_cache=(val)
         | 
| 59 | 
            -
                  message_list_cache[[@mailbox, @limit, @query[1..-1], @all_search]] ||= val
         | 
| 60 | 
            -
                end
         | 
| 61 | 
            -
             | 
| 62 49 | 
             
                def open
         | 
| 63 50 | 
             
                  @imap = Net::IMAP.new(@imap_server, @imap_port, true, nil, false)
         | 
| 64 51 | 
             
                  log @imap.login(@username, @password)
         | 
| @@ -101,7 +88,7 @@ module Vmail | |
| 101 88 | 
             
                def clear_cached_message
         | 
| 102 89 | 
             
                  log "CLEARING CACHED MESSAGE"
         | 
| 103 90 | 
             
                  @current_mail = nil
         | 
| 104 | 
            -
                  @ | 
| 91 | 
            +
                  @current_message_uid = nil
         | 
| 105 92 | 
             
                  @current_message = nil
         | 
| 106 93 | 
             
                end
         | 
| 107 94 |  | 
| @@ -137,7 +124,6 @@ module Vmail | |
| 137 124 | 
             
                    # this is just to prime the IMAP connection
         | 
| 138 125 | 
             
                    # It's necessary for some reason before update and deliver. 
         | 
| 139 126 | 
             
                    log "priming connection"
         | 
| 140 | 
            -
                    
         | 
| 141 127 | 
             
                    res = @imap.fetch(@ids[-1], ["ENVELOPE"])
         | 
| 142 128 | 
             
                    if res.nil?
         | 
| 143 129 | 
             
                      # just go ahead, just log
         | 
| @@ -193,33 +179,11 @@ module Vmail | |
| 193 179 | 
             
                    log(error) && raise(error)
         | 
| 194 180 | 
             
                  end
         | 
| 195 181 | 
             
                  log "- extracting headers"
         | 
| 196 | 
            -
                  log "- current message list cache has #{current_message_list_cache.size} items"
         | 
| 197 182 | 
             
                  new_message_rows = results.map {|x| extract_row_data(x) }
         | 
| 198 | 
            -
                  if are_uids
         | 
| 199 | 
            -
                    # replace old row_text values
         | 
| 200 | 
            -
                    new_message_rows.each {|new_row_data|
         | 
| 201 | 
            -
                      current_message_list_cache.
         | 
| 202 | 
            -
                        select {|old_row_data| old_row_data[:uid] == new_row_data[:uid]}.
         | 
| 203 | 
            -
                        each {|old_row_data| old_row_data[:row_text] = new_row_data[:row_text]}
         | 
| 204 | 
            -
                    }
         | 
| 205 | 
            -
                  else
         | 
| 206 | 
            -
                    if is_update
         | 
| 207 | 
            -
                      log "- adding messages from update to end of list"
         | 
| 208 | 
            -
                      current_message_list_cache.concat new_message_rows
         | 
| 209 | 
            -
                    else
         | 
| 210 | 
            -
                      # this adds old messages to the top of the list
         | 
| 211 | 
            -
                      # put new rows before the current ones
         | 
| 212 | 
            -
                      log "- adding more messages to head of list"
         | 
| 213 | 
            -
                      self.current_message_list_cache.unshift(*new_message_rows)
         | 
| 214 | 
            -
                    end
         | 
| 215 | 
            -
                  end
         | 
| 216 | 
            -
                  log "- new current message list cache has #{current_message_list_cache.size} items"
         | 
| 217 | 
            -
                  # current_message_list_cache is automatically cached to keyed message_list_cache
         | 
| 218 183 | 
             
                  log "- returning #{new_message_rows.size} new rows and caching result"  
         | 
| 219 184 | 
             
                  new_message_rows
         | 
| 220 185 | 
             
                end
         | 
| 221 186 |  | 
| 222 | 
            -
             | 
| 223 187 | 
             
                # TODO extract this to another class or module and write unit tests
         | 
| 224 188 | 
             
                def extract_row_data(fetch_data)
         | 
| 225 189 | 
             
                  seqno = fetch_data.seqno
         | 
| @@ -262,11 +226,13 @@ module Vmail | |
| 262 226 | 
             
                  mid_width = @width - 38
         | 
| 263 227 | 
             
                  address_col_width = (mid_width * 0.3).ceil
         | 
| 264 228 | 
             
                  subject_col_width = (mid_width * 0.7).floor
         | 
| 229 | 
            +
                  seqno_uid = [seqno.to_i, uid.to_i].join(':')
         | 
| 265 230 | 
             
                  row_text = [ flags.col(2),
         | 
| 266 231 | 
             
                               (date_formatted || '').col(14),
         | 
| 267 232 | 
             
                               address.col(address_col_width),
         | 
| 268 233 | 
             
                               subject.col(subject_col_width), 
         | 
| 269 | 
            -
                               number_to_human_size(size).rcol( | 
| 234 | 
            +
                               number_to_human_size(size).rcol(7), 
         | 
| 235 | 
            +
                               seqno_uid.to_s
         | 
| 270 236 | 
             
                  ].join(' | ')
         | 
| 271 237 | 
             
                  {:uid => uid, :seqno => seqno, :row_text => row_text}
         | 
| 272 238 | 
             
                rescue 
         | 
| @@ -317,13 +283,6 @@ module Vmail | |
| 317 283 | 
             
                  @query = query.map {|x| x.to_s.downcase}
         | 
| 318 284 | 
             
                  @limit = limit
         | 
| 319 285 | 
             
                  log "search query: #{@query.inspect}"
         | 
| 320 | 
            -
                  if !current_message_list_cache.empty?
         | 
| 321 | 
            -
                    log "- CACHE HIT"
         | 
| 322 | 
            -
                    res = current_message_list_cache.map {|x| x[:row_text]}.join("\n")
         | 
| 323 | 
            -
                    @ids = current_message_list_cache.map {|x| x[:seqno]}
         | 
| 324 | 
            -
                    return add_more_message_line(res, current_message_list_cache[0][:seqno])
         | 
| 325 | 
            -
                  end
         | 
| 326 | 
            -
                  log "- CACHE MISS" 
         | 
| 327 286 | 
             
                  log "- @all_search #{@all_search}"
         | 
| 328 287 | 
             
                  @query = query
         | 
| 329 288 | 
             
                  @ids = reconnect_if_necessary(180) do # increase timeout to 3 minutes
         | 
| @@ -336,14 +295,18 @@ module Vmail | |
| 336 295 | 
             
                                @start_index = [@ids.length - limit, 0].max
         | 
| 337 296 | 
             
                                @ids[@start_index..-1]
         | 
| 338 297 | 
             
                              end
         | 
| 339 | 
            -
                   | 
| 340 | 
            -
                   | 
| 341 | 
            -
                  current_message_list_cache = [] 
         | 
| 298 | 
            +
                  self.max_seqno = @ids[-1]
         | 
| 299 | 
            +
                  log "- search query got #{@ids.size} results; max seqno: #{self.max_seqno}" 
         | 
| 342 300 | 
             
                  clear_cached_message
         | 
| 343 301 | 
             
                  res = fetch_row_text(fetch_ids)
         | 
| 344 302 | 
             
                  add_more_message_line(res, fetch_ids[0])
         | 
| 345 303 | 
             
                end
         | 
| 346 304 |  | 
| 305 | 
            +
                def decrement_max_seqno(num)
         | 
| 306 | 
            +
                  log "Decremented max seqno from #{self.max_seqno} to #{self.max_seqno - num}"
         | 
| 307 | 
            +
                  self.max_seqno -= num
         | 
| 308 | 
            +
                end
         | 
| 309 | 
            +
             | 
| 347 310 | 
             
                def update
         | 
| 348 311 | 
             
                  prime_connection
         | 
| 349 312 | 
             
                  old_num_messages = @num_messages
         | 
| @@ -357,11 +320,9 @@ module Vmail | |
| 357 320 | 
             
                    log "search #update_query"
         | 
| 358 321 | 
             
                    @imap.search(update_query.join(' ')) 
         | 
| 359 322 | 
             
                  }
         | 
| 360 | 
            -
                  # TODO change this. will throw error now
         | 
| 361 | 
            -
                  max_seqno = current_message_list_cache[-1][:seqno]
         | 
| 362 323 | 
             
                  log "- got seqnos: #{ids.inspect}"
         | 
| 363 | 
            -
                  log "- getting seqnos > #{max_seqno}"
         | 
| 364 | 
            -
                  new_ids = ids.select {|seqno| seqno > max_seqno}
         | 
| 324 | 
            +
                  log "- getting seqnos > #{self.max_seqno}"
         | 
| 325 | 
            +
                  new_ids = ids.select {|seqno| seqno > self.max_seqno}
         | 
| 365 326 | 
             
                  @ids = @ids + new_ids
         | 
| 366 327 | 
             
                  log "- update: new uids: #{new_ids.inspect}"
         | 
| 367 328 | 
             
                  if !new_ids.empty?
         | 
| @@ -373,13 +334,13 @@ module Vmail | |
| 373 334 | 
             
                end
         | 
| 374 335 |  | 
| 375 336 | 
             
                # gets 100 messages prior to id
         | 
| 376 | 
            -
                def more_messages(limit=100)
         | 
| 377 | 
            -
                  message_id = current_message_list_cache[0][:seqno]
         | 
| 337 | 
            +
                def more_messages(message_id, limit=100)
         | 
| 378 338 | 
             
                  log "more_messages: message_id #{message_id}"
         | 
| 379 339 | 
             
                  message_id = message_id.to_i
         | 
| 380 340 | 
             
                  if @all_search 
         | 
| 381 341 | 
             
                    x = [(message_id - limit), 0].max
         | 
| 382 342 | 
             
                    y = [message_id - 1, 0].max
         | 
| 343 | 
            +
             | 
| 383 344 | 
             
                    res = fetch_row_text((x..y))
         | 
| 384 345 | 
             
                    add_more_message_line(res, x)
         | 
| 385 346 | 
             
                  else # filter search query
         | 
| @@ -392,63 +353,53 @@ module Vmail | |
| 392 353 | 
             
                  end
         | 
| 393 354 | 
             
                end
         | 
| 394 355 |  | 
| 395 | 
            -
                def add_more_message_line(res,  | 
| 396 | 
            -
                  log "add_more_message_line for  | 
| 356 | 
            +
                def add_more_message_line(res, start_seqno)
         | 
| 357 | 
            +
                  log "add_more_message_line for start_seqno #{start_seqno}"
         | 
| 397 358 | 
             
                  if @all_search
         | 
| 398 | 
            -
                    return res if  | 
| 399 | 
            -
                    remaining =  | 
| 359 | 
            +
                    return res if start_seqno.nil?
         | 
| 360 | 
            +
                    remaining = start_seqno - 1
         | 
| 400 361 | 
             
                  else # filter search
         | 
| 401 | 
            -
                    remaining = (@ids.index( | 
| 362 | 
            +
                    remaining = (@ids.index(start_seqno) || 1) - 1
         | 
| 402 363 | 
             
                  end
         | 
| 403 364 | 
             
                  if remaining < 1
         | 
| 404 365 | 
             
                    log "none remaining"
         | 
| 405 366 | 
             
                    return "showing all matches\n" + res
         | 
| 406 367 | 
             
                  end
         | 
| 407 368 | 
             
                  log "remaining messages: #{remaining}"
         | 
| 408 | 
            -
                  ">  Load #{[100, remaining].min} more messages. #{remaining} remaining | 
| 369 | 
            +
                  ">  Load #{[100, remaining].min} more messages. #{remaining} remaining. end seqno: #{start_seqno}\n" + res
         | 
| 409 370 | 
             
                end
         | 
| 410 371 |  | 
| 411 | 
            -
                def show_message( | 
| 412 | 
            -
                  log "show message: #{ | 
| 413 | 
            -
                  return if index.to_i < 0
         | 
| 372 | 
            +
                def show_message(uid, raw=false)
         | 
| 373 | 
            +
                  log "show message: #{uid}"
         | 
| 414 374 | 
             
                  return @current_mail.to_s if raw 
         | 
| 415 | 
            -
                   | 
| 416 | 
            -
                  if  | 
| 375 | 
            +
                  uid = uid.to_i
         | 
| 376 | 
            +
                  if uid == @current_message_uid 
         | 
| 417 377 | 
             
                    return @current_message 
         | 
| 418 378 | 
             
                  end
         | 
| 419 379 |  | 
| 420 | 
            -
                   | 
| 421 | 
            -
                    index -= 1
         | 
| 422 | 
            -
                    log "index beyond bounds, setting index to #{index}"
         | 
| 423 | 
            -
                  end
         | 
| 424 | 
            -
                  prefetch_adjacent(index) # TODO mark these as unread
         | 
| 380 | 
            +
                  #prefetch_adjacent(index) # deprecated
         | 
| 425 381 |  | 
| 426 | 
            -
                   | 
| 427 | 
            -
                   | 
| 428 | 
            -
             | 
| 429 | 
            -
                  end
         | 
| 430 | 
            -
                  # TODO factor this gsubbing stuff out into own function
         | 
| 431 | 
            -
                  envelope_data[:row_text] = envelope_data[:row_text].gsub(/^\+ /, '  ').gsub(/^\*\+/, '* ') # mark as read in cache
         | 
| 432 | 
            -
                  seqno = envelope_data[:seqno]
         | 
| 433 | 
            -
                  uid = envelope_data[:uid] 
         | 
| 434 | 
            -
                  log "showing message index: #{index} seqno: #{seqno} uid #{uid}"
         | 
| 382 | 
            +
                  # TODO keep state in vim buffers, instead of on Vmail Ruby client
         | 
| 383 | 
            +
                  # envelope_data[:row_text] = envelope_data[:row_text].gsub(/^\+ /, '  ').gsub(/^\*\+/, '* ') # mark as read in cache
         | 
| 384 | 
            +
                  #seqno = envelope_data[:seqno]
         | 
| 435 385 |  | 
| 386 | 
            +
                  log "showing message uid: #{uid}"
         | 
| 436 387 | 
             
                  data = if x = message_cache[[@mailbox, uid]]
         | 
| 437 388 | 
             
                           log "- message cache hit"
         | 
| 438 389 | 
             
                           x
         | 
| 439 390 | 
             
                         else 
         | 
| 440 391 | 
             
                           log "- fetching and storing to message_cache[[#{@mailbox}, #{uid}]]"
         | 
| 441 | 
            -
                           fetch_and_cache( | 
| 392 | 
            +
                           fetch_and_cache(uid)
         | 
| 442 393 | 
             
                         end
         | 
| 443 394 | 
             
                  if data.nil?
         | 
| 444 395 | 
             
                    # retry, though this is a hack!
         | 
| 445 396 | 
             
                    log "- data is nil. retrying..."
         | 
| 446 | 
            -
                    return show_message( | 
| 397 | 
            +
                    return show_message(uid, raw)
         | 
| 447 398 | 
             
                  end
         | 
| 448 399 | 
             
                  # make this more DRY later by directly using a ref to the hash
         | 
| 449 400 | 
             
                  mail = data[:mail]
         | 
| 450 401 | 
             
                  size = data[:size] 
         | 
| 451 | 
            -
                  @ | 
| 402 | 
            +
                  @current_message_uid = uid
         | 
| 452 403 | 
             
                  log "- setting @current_mail"
         | 
| 453 404 | 
             
                  @current_mail = mail # used later to show raw message or extract attachments if any
         | 
| 454 405 | 
             
                  @current_message = data[:message_text]
         | 
| @@ -457,14 +408,11 @@ module Vmail | |
| 457 408 | 
             
                  "Error encountered parsing this message:\n#{$!}\n#{$!.backtrace.join("\n")}"
         | 
| 458 409 | 
             
                end
         | 
| 459 410 |  | 
| 460 | 
            -
                def fetch_and_cache( | 
| 461 | 
            -
                   | 
| 462 | 
            -
             | 
| 463 | 
            -
                   | 
| 464 | 
            -
                  uid = envelope_data[:uid] 
         | 
| 465 | 
            -
                  return if message_cache[[@mailbox, uid]] 
         | 
| 411 | 
            +
                def fetch_and_cache(uid)
         | 
| 412 | 
            +
                  if data = message_cache[[@mailbox, uid]] 
         | 
| 413 | 
            +
                    return data
         | 
| 414 | 
            +
                  end
         | 
| 466 415 | 
             
                  fetch_data = reconnect_if_necessary do 
         | 
| 467 | 
            -
                    # log "@imap.uid_fetch #{uid}"
         | 
| 468 416 | 
             
                    res = @imap.uid_fetch(uid, ["FLAGS", "RFC822", "RFC822.SIZE"])
         | 
| 469 417 | 
             
                    if res.nil?
         | 
| 470 418 | 
             
                      # retry one more time ( find a more elegant way to do this )
         | 
| @@ -472,32 +420,29 @@ module Vmail | |
| 472 420 | 
             
                    end
         | 
| 473 421 | 
             
                    res[0] 
         | 
| 474 422 | 
             
                  end
         | 
| 475 | 
            -
                  #  | 
| 476 | 
            -
                  if envelope_data[:row_text] =~ /^\*?\+/ # not seen
         | 
| 477 | 
            -
                    log "reflagging index #{index} uid #{uid} as not seen"
         | 
| 478 | 
            -
                    flag("#{index}..#{index}", '-FLAGS', :Seen) # change flag() method to accept a single index later
         | 
| 479 | 
            -
                  end
         | 
| 423 | 
            +
                  # USE THIS
         | 
| 480 424 | 
             
                  size = fetch_data.attr["RFC822.SIZE"]
         | 
| 481 425 | 
             
                  mail = Mail.new(fetch_data.attr['RFC822'])
         | 
| 482 426 | 
             
                  formatter = Vmail::MessageFormatter.new(mail)
         | 
| 483 427 | 
             
                  message_text = <<-EOF
         | 
| 484 | 
            -
            #{@mailbox}  | 
| 428 | 
            +
            #{@mailbox} uid:#{uid} #{number_to_human_size size} #{format_parts_info(formatter.list_parts)}
         | 
| 485 429 | 
             
            #{divider '-'}
         | 
| 486 430 | 
             
            #{format_headers(formatter.extract_headers)}
         | 
| 487 431 |  | 
| 488 432 | 
             
            #{formatter.process_body}
         | 
| 489 433 | 
             
            EOF
         | 
| 490 434 | 
             
                  # log "storing message_cache[[#{@mailbox}, #{uid}]]"
         | 
| 491 | 
            -
                  d = {:mail => mail, :size => size, :message_text => message_text}
         | 
| 435 | 
            +
                  d = {:mail => mail, :size => size, :message_text => message_text, :seqno => fetch_data.seqno}
         | 
| 492 436 | 
             
                  message_cache[[@mailbox, uid]] = d
         | 
| 493 437 | 
             
                rescue
         | 
| 494 | 
            -
                  msg = "Error encountered parsing message  | 
| 438 | 
            +
                  msg = "Error encountered parsing message uid  #{uid}:\n#{$!}\n#{$!.backtrace.join("\n")}" + 
         | 
| 495 439 | 
             
                    "\n\nRaw message:\n\n" + mail.to_s
         | 
| 496 440 | 
             
                  log msg
         | 
| 497 441 | 
             
                  log message_text
         | 
| 498 442 | 
             
                  {:message_text => msg}
         | 
| 499 443 | 
             
                end
         | 
| 500 444 |  | 
| 445 | 
            +
                # deprecated
         | 
| 501 446 | 
             
                def prefetch_adjacent(index)
         | 
| 502 447 | 
             
                  Thread.new do 
         | 
| 503 448 | 
             
                    [index + 1, index - 1].each do |idx|
         | 
| @@ -515,11 +460,12 @@ EOF | |
| 515 460 |  | 
| 516 461 | 
             
                # id_set is a string comming from the vim client
         | 
| 517 462 | 
             
                # action is -FLAGS or +FLAGS
         | 
| 518 | 
            -
                def flag( | 
| 519 | 
            -
                  uid_set =  | 
| 463 | 
            +
                def flag(uid_set, action, flg)
         | 
| 464 | 
            +
                  uid_set = uid_set.split(',').map(&:to_i)
         | 
| 520 465 | 
             
                  log "flag #{uid_set.inspect} #{flg} #{action}"
         | 
| 521 466 | 
             
                  if flg == 'Deleted'
         | 
| 522 | 
            -
                    log "Deleting  | 
| 467 | 
            +
                    log "Deleting uid_set: #{uid_set.inspect}"
         | 
| 468 | 
            +
                    decrement_max_seqno(uid_set.size)
         | 
| 523 469 | 
             
                    # for delete, do in a separate thread because deletions are slow
         | 
| 524 470 | 
             
                    Thread.new do 
         | 
| 525 471 | 
             
                      unless @mailbox == '[Gmail]/Trash'
         | 
| @@ -528,87 +474,34 @@ EOF | |
| 528 474 | 
             
                      end
         | 
| 529 475 | 
             
                      log "@imap.uid_store #{uid_set.inspect} #{action} [#{flg.to_sym}]"
         | 
| 530 476 | 
             
                      log @imap.uid_store(uid_set, action, [flg.to_sym])
         | 
| 531 | 
            -
                      remove_uid_set_from_cached_lists(uid_set)
         | 
| 532 477 | 
             
                      reload_mailbox
         | 
| 533 478 | 
             
                      clear_cached_message
         | 
| 534 479 | 
             
                    end
         | 
| 535 480 | 
             
                  elsif flg == '[Gmail]/Spam'
         | 
| 536 | 
            -
                    log "Marking as spam  | 
| 481 | 
            +
                    log "Marking as spam uid_set: #{uid_set.inspect}"
         | 
| 482 | 
            +
                    decrement_max_seqno(uid_set.size)
         | 
| 537 483 | 
             
                    Thread.new do 
         | 
| 538 484 | 
             
                      log "@imap.uid_copy #{uid_set.inspect} to spam"
         | 
| 539 485 | 
             
                      log @imap.uid_copy(uid_set, "[Gmail]/Spam")
         | 
| 540 486 | 
             
                      log "@imap.uid_store #{uid_set.inspect} #{action} [:Deleted]"
         | 
| 541 487 | 
             
                      log @imap.uid_store(uid_set, action, [:Deleted])
         | 
| 542 | 
            -
                      remove_uid_set_from_cached_lists(uid_set)
         | 
| 543 488 | 
             
                      reload_mailbox
         | 
| 544 489 | 
             
                      clear_cached_message
         | 
| 545 490 | 
             
                    end
         | 
| 546 491 | 
             
                    "#{id} deleted"
         | 
| 547 492 | 
             
                  else
         | 
| 548 | 
            -
                    log "Flagging  | 
| 493 | 
            +
                    log "Flagging uid_set: #{uid_set.inspect}"
         | 
| 549 494 | 
             
                    Thread.new do
         | 
| 550 495 | 
             
                      log "@imap.uid_store #{uid_set.inspect} #{action} [#{flg.to_sym}]"
         | 
| 551 496 | 
             
                      log @imap.uid_store(uid_set, action, [flg.to_sym])
         | 
| 552 497 | 
             
                    end
         | 
| 553 | 
            -
             | 
| 554 | 
            -
                    # mark cached versions of the rows with flag/unflag
         | 
| 555 | 
            -
                    uid_set.each do |uid|
         | 
| 556 | 
            -
                      envelope_data = current_message_list_cache.detect {|x| x[:uid] == uid}
         | 
| 557 | 
            -
                      if action == '+FLAGS' && flg == 'Flagged'
         | 
| 558 | 
            -
                        envelope_data[:row_text] = envelope_data[:row_text].gsub(/^\+ /, '*+').gsub(/^  /, '* ') # mark as read in cache
         | 
| 559 | 
            -
                      elsif action == '-FLAGS' && flg == 'Flagged'
         | 
| 560 | 
            -
                        envelope_data[:row_text] = envelope_data[:row_text].gsub(/^\*\+/, '+ ').gsub(/^\* /, '  ') # mark as read in cache
         | 
| 561 | 
            -
                      end
         | 
| 562 | 
            -
                    end
         | 
| 563 | 
            -
             | 
| 564 | 
            -
                  end
         | 
| 565 | 
            -
                end
         | 
| 566 | 
            -
             | 
| 567 | 
            -
                def uids_from_index_range(index_range_as_string)
         | 
| 568 | 
            -
                  raise "expecting String" unless index_range_as_string.is_a?(String)
         | 
| 569 | 
            -
                  raise "expecting a range as string" unless index_range_as_string =~ /^\d+\.\.\d+$/ 
         | 
| 570 | 
            -
                  log "converting index_range #{index_range_as_string} to uids"
         | 
| 571 | 
            -
                  uids = current_message_list_cache[eval(index_range_as_string)].map {|row| row[:uid]}
         | 
| 572 | 
            -
                  log "converted index_range #{index_range_as_string} to uids #{uids.inspect}"
         | 
| 573 | 
            -
                  uids
         | 
| 574 | 
            -
                end
         | 
| 575 | 
            -
             | 
| 576 | 
            -
                def remove_uid_set_from_cached_lists(uid_set)
         | 
| 577 | 
            -
                  # delete from cached @ids and current_message_list_cache
         | 
| 578 | 
            -
                  seqnos_to_delete = []
         | 
| 579 | 
            -
                  uid_set.each {|uid| 
         | 
| 580 | 
            -
                    row = current_message_list_cache.detect {|row| row[:uid] == uid}
         | 
| 581 | 
            -
                    seqno = row[:seqno]
         | 
| 582 | 
            -
                    log "deleting seqno #{seqno} from @ids"
         | 
| 583 | 
            -
                    @ids.delete seqno
         | 
| 584 | 
            -
                    seqnos_to_delete << seqno
         | 
| 585 | 
            -
                  }
         | 
| 586 | 
            -
                  log "seqnos_to_delete: #{seqnos_to_delete.inspect}"
         | 
| 587 | 
            -
                  seqnos_to_delete.reverse.each do |seqno|
         | 
| 588 | 
            -
                    startsize = current_message_list_cache.size 
         | 
| 589 | 
            -
                    log "deleting row with seqno #{seqno}"
         | 
| 590 | 
            -
                    current_message_list_cache.delete_if {|x| x[:seqno] == seqno}
         | 
| 591 | 
            -
                    endsize = current_message_list_cache.size
         | 
| 592 | 
            -
                    log "deleted #{startsize - endsize} rows"
         | 
| 593 | 
            -
                  end
         | 
| 594 | 
            -
                  # now we need to decrement all the higher sequence numbers!
         | 
| 595 | 
            -
                  basenum = seqnos_to_delete.min  # this is the lowested seqno deleted
         | 
| 596 | 
            -
                  diff = seqnos_to_delete.size # substract this from all seqnos >= basenum
         | 
| 597 | 
            -
                  changes = []
         | 
| 598 | 
            -
                  current_message_list_cache.each do |row|
         | 
| 599 | 
            -
                    if row[:seqno] >= basenum
         | 
| 600 | 
            -
                      changes << "#{row[:seqno]}->#{row[:seqno] - diff}"
         | 
| 601 | 
            -
                      row[:seqno] -= diff
         | 
| 602 | 
            -
                    end
         | 
| 603 498 | 
             
                  end
         | 
| 604 | 
            -
                  log "seqno decremented: #{changes.join(";")}"
         | 
| 605 | 
            -
                rescue
         | 
| 606 | 
            -
                  log "error removing uid set from cached lists"
         | 
| 607 | 
            -
                  log $!
         | 
| 608 499 | 
             
                end
         | 
| 609 500 |  | 
| 610 | 
            -
                def move_to( | 
| 611 | 
            -
                   | 
| 501 | 
            +
                def move_to(uid_set, mailbox)
         | 
| 502 | 
            +
                  uid_set = uid_set.split(',').map(&:to_i)
         | 
| 503 | 
            +
                  decrement_max_seqno(uid_set.size)
         | 
| 504 | 
            +
                  log "move #{uid_set.inspect} to #{mailbox}"
         | 
| 612 505 | 
             
                  if mailbox == 'all'
         | 
| 613 506 | 
             
                    log "archiving messages"
         | 
| 614 507 | 
             
                  end
         | 
| @@ -616,25 +509,22 @@ EOF | |
| 616 509 | 
             
                    mailbox = MailboxAliases[mailbox]
         | 
| 617 510 | 
             
                  end
         | 
| 618 511 | 
             
                  create_if_necessary mailbox
         | 
| 619 | 
            -
                  log "getting uids form index range #{id_set}"
         | 
| 620 | 
            -
                  uid_set = uids_from_index_range(id_set)
         | 
| 621 512 | 
             
                  log "moving uid_set: #{uid_set.inspect} to #{mailbox}"
         | 
| 622 513 | 
             
                  Thread.new do 
         | 
| 623 514 | 
             
                    log @imap.uid_copy(uid_set, mailbox)
         | 
| 624 515 | 
             
                    log @imap.uid_store(uid_set, '+FLAGS', [:Deleted])
         | 
| 625 | 
            -
                    remove_uid_set_from_cached_lists(uid_set)
         | 
| 626 516 | 
             
                    reload_mailbox
         | 
| 627 517 | 
             
                    clear_cached_message
         | 
| 628 518 | 
             
                    log "moved uid_set #{uid_set.inspect} to #{mailbox}"
         | 
| 629 519 | 
             
                  end
         | 
| 630 520 | 
             
                end
         | 
| 631 521 |  | 
| 632 | 
            -
                def copy_to( | 
| 522 | 
            +
                def copy_to(uid_set, mailbox)
         | 
| 523 | 
            +
                  uid_set = uid_set.split(',').map(&:to_i)
         | 
| 633 524 | 
             
                  if MailboxAliases[mailbox]
         | 
| 634 525 | 
             
                    mailbox = MailboxAliases[mailbox]
         | 
| 635 526 | 
             
                  end
         | 
| 636 527 | 
             
                  create_if_necessary mailbox
         | 
| 637 | 
            -
                  uid_set = uids_from_index_range(id_set)
         | 
| 638 528 | 
             
                  log "copying #{uid_set.inspect} to #{mailbox}"
         | 
| 639 529 | 
             
                  Thread.new do 
         | 
| 640 530 | 
             
                    log @imap.uid_copy(uid_set, mailbox)
         | 
| @@ -653,20 +543,18 @@ EOF | |
| 653 543 | 
             
                  end
         | 
| 654 544 | 
             
                end
         | 
| 655 545 |  | 
| 656 | 
            -
                def append_to_file(file,  | 
| 657 | 
            -
                   | 
| 658 | 
            -
                   | 
| 659 | 
            -
                   | 
| 660 | 
            -
             | 
| 661 | 
            -
                    message = show_message(idx)
         | 
| 546 | 
            +
                def append_to_file(file, uid_set)
         | 
| 547 | 
            +
                  uid_set = uid_set.split(',').map(&:to_i)
         | 
| 548 | 
            +
                  log "append to file uid set #{uid_set.inspect} to file: #{file}"
         | 
| 549 | 
            +
                  uid_set.each do |uid|
         | 
| 550 | 
            +
                    message = show_message(uid)
         | 
| 662 551 | 
             
                    File.open(file, 'a') {|f| f.puts(divider('=') + "\n" + message + "\n\n")}
         | 
| 663 552 | 
             
                    subject = (message[/^subject:(.*)/,1] || '').strip
         | 
| 664 553 | 
             
                    log "appended message '#{subject}'"
         | 
| 665 554 | 
             
                  end
         | 
| 666 | 
            -
                  "printed #{ | 
| 555 | 
            +
                  "printed #{uid_set.size} message#{uid_set.size == 1 ? '' : 's'} to #{file.strip}"
         | 
| 667 556 | 
             
                end
         | 
| 668 557 |  | 
| 669 | 
            -
             | 
| 670 558 | 
             
                def new_message_template(subject = nil, append_signature = true)
         | 
| 671 559 | 
             
                  headers = {'from' => "#{@name} <#{@username}>",
         | 
| 672 560 | 
             
                    'to' => nil,
         | 
| @@ -758,7 +646,6 @@ EOF | |
| 758 646 | 
             
                  # attachments are added as a snippet of YAML after a blank line
         | 
| 759 647 | 
             
                  # after the headers, and followed by a blank line
         | 
| 760 648 | 
             
                  if (attachments = raw_body.split(/\n\s*\n/, 2)[0]) =~ /^attach(ment|ments)*:/
         | 
| 761 | 
            -
                    # TODO
         | 
| 762 649 | 
             
                    files = YAML::load(attachments).values.flatten
         | 
| 763 650 | 
             
                    log "attach: #{files}"
         | 
| 764 651 | 
             
                    files.each do |file|
         | 
    
        data/lib/vmail/version.rb
    CHANGED
    
    
    
        data/website/vmail-template.html
    CHANGED
    
    | @@ -38,12 +38,23 @@ | |
| 38 38 | 
             
                          <li><a href="https://github.com/danchoi/vmail/issues">issue tracker</a></li>
         | 
| 39 39 | 
             
                          <li><a href="https://github.com/danchoi/vmail/commits/master">commit history</a></li>
         | 
| 40 40 | 
             
                          <li><a href="https://github.com/danchoi/vmail/wiki">wiki</a></li>
         | 
| 41 | 
            -
                          <li><a href="http://twitter.com/#!/search/vmail%20gmail">tweets about</a></li>
         | 
| 42 41 | 
             
                        </ul>
         | 
| 42 | 
            +
             | 
| 43 43 | 
             
                        <h4>share this</h4>
         | 
| 44 44 |  | 
| 45 45 | 
             
            <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>
         | 
| 46 46 |  | 
| 47 | 
            +
             | 
| 48 | 
            +
                        <div class="also-by">
         | 
| 49 | 
            +
                          <h4>also by this developer</h4>
         | 
| 50 | 
            +
                          <ul class="cross-promo">
         | 
| 51 | 
            +
                            <li><a href="http://instantwatcher.com">instantwatcher.com<a/></li>
         | 
| 52 | 
            +
                            <li><a href="http://openmbta.org">OpenMBTA<a/></li>
         | 
| 53 | 
            +
                            <li><a href="http://kindlefeeder.com">kindlefeeder.com<a/></li>
         | 
| 54 | 
            +
                          </ul>
         | 
| 55 | 
            +
                        </div>
         | 
| 56 | 
            +
             | 
| 57 | 
            +
             | 
| 47 58 | 
             
                      </div>
         | 
| 48 59 |  | 
| 49 60 | 
             
                  </div>
         | 
    
        metadata
    CHANGED
    
    | @@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version | |
| 4 4 | 
             
              prerelease: false
         | 
| 5 5 | 
             
              segments: 
         | 
| 6 6 | 
             
              - 1
         | 
| 7 | 
            -
              -  | 
| 8 | 
            -
              -  | 
| 9 | 
            -
              version: 1. | 
| 7 | 
            +
              - 2
         | 
| 8 | 
            +
              - 0
         | 
| 9 | 
            +
              version: 1.2.0
         | 
| 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:  | 
| 17 | 
            +
            date: 2011-01-06 00:00:00 -05:00
         | 
| 18 18 | 
             
            default_executable: 
         | 
| 19 19 | 
             
            dependencies: 
         | 
| 20 20 | 
             
            - !ruby/object:Gem::Dependency 
         |