sup 0.8.1 → 0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/CONTRIBUTORS +13 -6
- data/History.txt +19 -0
- data/ReleaseNotes +35 -0
- data/bin/sup +82 -77
- data/bin/sup-add +7 -7
- data/bin/sup-config +104 -85
- data/bin/sup-dump +4 -5
- data/bin/sup-recover-sources +9 -10
- data/bin/sup-sync +121 -100
- data/bin/sup-sync-back +18 -15
- data/bin/sup-tweak-labels +24 -21
- data/lib/sup.rb +53 -33
- data/lib/sup/account.rb +0 -2
- data/lib/sup/buffer.rb +47 -22
- data/lib/sup/colormap.rb +6 -6
- data/lib/sup/contact.rb +0 -2
- data/lib/sup/crypto.rb +34 -23
- data/lib/sup/draft.rb +6 -14
- data/lib/sup/ferret_index.rb +471 -0
- data/lib/sup/hook.rb +30 -43
- data/lib/sup/hook.rb.BACKUP.8625.rb +158 -0
- data/lib/sup/hook.rb.BACKUP.8681.rb +158 -0
- data/lib/sup/hook.rb.BASE.8625.rb +155 -0
- data/lib/sup/hook.rb.BASE.8681.rb +155 -0
- data/lib/sup/hook.rb.LOCAL.8625.rb +142 -0
- data/lib/sup/hook.rb.LOCAL.8681.rb +142 -0
- data/lib/sup/hook.rb.REMOTE.8625.rb +145 -0
- data/lib/sup/hook.rb.REMOTE.8681.rb +145 -0
- data/lib/sup/imap.rb +18 -8
- data/lib/sup/index.rb +70 -528
- data/lib/sup/interactive-lock.rb +74 -0
- data/lib/sup/keymap.rb +26 -26
- data/lib/sup/label.rb +2 -4
- data/lib/sup/logger.rb +54 -35
- data/lib/sup/maildir.rb +41 -6
- data/lib/sup/mbox.rb +1 -1
- data/lib/sup/mbox/loader.rb +18 -6
- data/lib/sup/mbox/ssh-file.rb +1 -7
- data/lib/sup/message-chunks.rb +36 -23
- data/lib/sup/message.rb +126 -46
- data/lib/sup/mode.rb +3 -2
- data/lib/sup/modes/console-mode.rb +108 -0
- data/lib/sup/modes/edit-message-mode.rb +15 -5
- data/lib/sup/modes/inbox-mode.rb +2 -4
- data/lib/sup/modes/label-list-mode.rb +1 -1
- data/lib/sup/modes/line-cursor-mode.rb +18 -18
- data/lib/sup/modes/log-mode.rb +29 -16
- data/lib/sup/modes/poll-mode.rb +7 -9
- data/lib/sup/modes/reply-mode.rb +5 -3
- data/lib/sup/modes/scroll-mode.rb +2 -2
- data/lib/sup/modes/search-results-mode.rb +9 -11
- data/lib/sup/modes/text-mode.rb +2 -2
- data/lib/sup/modes/thread-index-mode.rb +26 -16
- data/lib/sup/modes/thread-view-mode.rb +84 -39
- data/lib/sup/person.rb +6 -8
- data/lib/sup/poll.rb +46 -47
- data/lib/sup/rfc2047.rb +1 -5
- data/lib/sup/sent.rb +27 -20
- data/lib/sup/source.rb +90 -13
- data/lib/sup/textfield.rb +4 -4
- data/lib/sup/thread.rb +15 -13
- data/lib/sup/undo.rb +0 -1
- data/lib/sup/update.rb +0 -1
- data/lib/sup/util.rb +51 -43
- data/lib/sup/xapian_index.rb +566 -0
- metadata +57 -46
- data/lib/sup/suicide.rb +0 -36
    
        data/bin/sup-dump
    CHANGED
    
    | @@ -21,11 +21,10 @@ No options. | |
| 21 21 | 
             
            EOS
         | 
| 22 22 | 
             
            end
         | 
| 23 23 |  | 
| 24 | 
            -
            index = Redwood::Index. | 
| 24 | 
            +
            index = Redwood::Index.init
         | 
| 25 | 
            +
            Redwood::SourceManager.init
         | 
| 25 26 | 
             
            index.load
         | 
| 26 27 |  | 
| 27 | 
            -
             | 
| 28 | 
            -
               | 
| 29 | 
            -
              d = index.index[i]
         | 
| 30 | 
            -
              puts [d[:message_id], "(" + d[:label] + ")"] * " "
         | 
| 28 | 
            +
            index.each_message :load_spam => true, :load_deleted => true, :load_killed => true do |m|
         | 
| 29 | 
            +
              puts "#{m.id} (#{m.labels.to_a.sort_by { |l| l.to_s } * ' '})"
         | 
| 31 30 | 
             
            end
         | 
    
        data/bin/sup-recover-sources
    CHANGED
    
    | @@ -48,13 +48,14 @@ EOS | |
| 48 48 | 
             
            end.parse(ARGV)
         | 
| 49 49 |  | 
| 50 50 | 
             
            require "sup"
         | 
| 51 | 
            +
            Redwood::start
         | 
| 51 52 | 
             
            puts "loading index..."
         | 
| 52 | 
            -
            index = Redwood::Index. | 
| 53 | 
            +
            index = Redwood::Index.init
         | 
| 53 54 | 
             
            index.load
         | 
| 54 55 | 
             
            puts "loaded index of #{index.size} messages"
         | 
| 55 56 |  | 
| 56 57 | 
             
            ARGV.each do |fn|
         | 
| 57 | 
            -
              next if  | 
| 58 | 
            +
              next if Redwood::SourceManager.source_for fn
         | 
| 58 59 |  | 
| 59 60 | 
             
              ## TODO: merge this code with the same snippet in import
         | 
| 60 61 | 
             
              source = 
         | 
| @@ -69,15 +70,14 @@ ARGV.each do |fn| | |
| 69 70 | 
             
                  Redwood::MBox::Loader.new(fn, nil, !$opts[:unusual], $opts[:archive])
         | 
| 70 71 | 
             
                end
         | 
| 71 72 |  | 
| 72 | 
            -
              source_ids =  | 
| 73 | 
            +
              source_ids = Hash.new 0
         | 
| 73 74 | 
             
              count = 0
         | 
| 74 75 | 
             
              source.each do |offset, labels|
         | 
| 75 76 | 
             
                m = Redwood::Message.new :source => source, :source_info => offset
         | 
| 76 | 
            -
                 | 
| 77 | 
            -
                 | 
| 78 | 
            -
                 | 
| 79 | 
            -
             | 
| 80 | 
            -
                source_ids[entry[:source_id]] = (source_ids[entry[:source_id]] || 0) + 1
         | 
| 77 | 
            +
                m.load_from_source!
         | 
| 78 | 
            +
                source_id = Redwood::SourceManager.source_for_id m.id
         | 
| 79 | 
            +
                next unless source_id
         | 
| 80 | 
            +
                source_ids[source_id] += 1
         | 
| 81 81 | 
             
                count += 1
         | 
| 82 82 | 
             
                break if count == $opts[:scan_num]
         | 
| 83 83 | 
             
              end
         | 
| @@ -86,8 +86,7 @@ ARGV.each do |fn| | |
| 86 86 | 
             
                id = source_ids.keys.first.to_i
         | 
| 87 87 | 
             
                puts "assigned #{source} to #{source_ids.keys.first}"
         | 
| 88 88 | 
             
                source.id = id
         | 
| 89 | 
            -
                 | 
| 90 | 
            -
                index.add_source source
         | 
| 89 | 
            +
                Redwood::SourceManager.add_source source
         | 
| 91 90 | 
             
              else
         | 
| 92 91 | 
             
                puts ">> unable to determine #{source}: #{source_ids.inspect}"
         | 
| 93 92 | 
             
              end
         | 
    
        data/bin/sup-sync
    CHANGED
    
    | @@ -9,9 +9,7 @@ PROGRESS_UPDATE_INTERVAL = 15 # seconds | |
| 9 9 |  | 
| 10 10 | 
             
            class Float
         | 
| 11 11 | 
             
              def to_s; sprintf '%.2f', self; end
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                 infinite? ? "unknown" : super
         | 
| 14 | 
            -
               end
         | 
| 12 | 
            +
              def to_time_s; infinite? ? "unknown" : super end
         | 
| 15 13 | 
             
            end
         | 
| 16 14 |  | 
| 17 15 | 
             
            class Numeric
         | 
| @@ -21,6 +19,10 @@ class Numeric | |
| 21 19 | 
             
              end
         | 
| 22 20 | 
             
            end
         | 
| 23 21 |  | 
| 22 | 
            +
            class Set
         | 
| 23 | 
            +
              def to_s; to_a * ',' end
         | 
| 24 | 
            +
            end
         | 
| 25 | 
            +
             | 
| 24 26 | 
             
            def time
         | 
| 25 27 | 
             
              startt = Time.now
         | 
| 26 28 | 
             
              yield
         | 
| @@ -54,10 +56,10 @@ by running "sup-add --help". | |
| 54 56 | 
             
            Options controlling WHICH messages sup-sync operates on:
         | 
| 55 57 | 
             
            EOS
         | 
| 56 58 | 
             
              opt :new, "Operate on new messages only. Don't scan over the entire source. (Default.)", :short => :none
         | 
| 57 | 
            -
              opt :changed, "Scan over the entire source for messages that have been deleted, altered, or moved from another source. | 
| 59 | 
            +
              opt :changed, "Scan over the entire source for messages that have been deleted, altered, or moved from another source."
         | 
| 58 60 | 
             
              opt :restored, "Operate only on those messages included in a dump file as specified by --restore which have changed state."
         | 
| 59 61 | 
             
              opt :all, "Operate on all messages in the source, regardless of newness or changedness."
         | 
| 60 | 
            -
              opt :start_at, "For --changed and --all, start at a particular offset.", :type => :int
         | 
| 62 | 
            +
              opt :start_at, "For --changed, --restored and --all, start at a particular offset.", :type => :int
         | 
| 61 63 |  | 
| 62 64 | 
             
            text <<EOS
         | 
| 63 65 |  | 
| @@ -68,7 +70,7 @@ EOS | |
| 68 70 | 
             
              opt :discard, "Discard any message state in the index and use the default source state. Dangerous!", :short => :none
         | 
| 69 71 | 
             
              opt :archive, "When using the default source state, mark messages as archived.", :short => "-x"
         | 
| 70 72 | 
             
              opt :read, "When using the default source state, mark messages as read."
         | 
| 71 | 
            -
              opt :extra_labels, "When using the default source state, also apply these user-defined labels | 
| 73 | 
            +
              opt :extra_labels, "When using the default source state, also apply these user-defined labels (a comma-separated list)", :default => "", :short => :none
         | 
| 72 74 |  | 
| 73 75 | 
             
            text <<EOS
         | 
| 74 76 |  | 
| @@ -86,42 +88,46 @@ end | |
| 86 88 | 
             
            Trollop::die :restored, "requires --restore" if opts[:restored] unless opts[:restore]
         | 
| 87 89 | 
             
            if opts[:start_at]
         | 
| 88 90 | 
             
              Trollop::die :start_at, "must be non-negative" if opts[:start_at] < 0
         | 
| 89 | 
            -
              Trollop::die :start_at, "requires either --changed or --all" unless opts[:changed] || opts[:all]
         | 
| 91 | 
            +
              Trollop::die :start_at, "requires either --changed, --restored or --all" unless opts[:changed] || opts[:restored] || opts[:all]
         | 
| 90 92 | 
             
            end
         | 
| 91 93 |  | 
| 92 94 | 
             
            target = [:new, :changed, :all, :restored].find { |x| opts[x] } || :new
         | 
| 93 95 | 
             
            op = [:asis, :restore, :discard].find { |x| opts[x] } || :asis
         | 
| 94 96 |  | 
| 95 97 | 
             
            Redwood::start
         | 
| 96 | 
            -
            index = Redwood::Index. | 
| 97 | 
            -
             | 
| 98 | 
            -
            restored_state =
         | 
| 99 | 
            -
               | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
                 | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
                  dump[mid] = labels.symbolistize
         | 
| 106 | 
            -
                end
         | 
| 107 | 
            -
                $stderr.puts "Read #{dump.size} entries from dump file."
         | 
| 108 | 
            -
                dump
         | 
| 109 | 
            -
              else
         | 
| 110 | 
            -
                {}
         | 
| 98 | 
            +
            index = Redwood::Index.init
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            restored_state = if opts[:restore]
         | 
| 101 | 
            +
              dump = {}
         | 
| 102 | 
            +
              puts "Loading state dump from #{opts[:restore]}..."
         | 
| 103 | 
            +
              IO.foreach opts[:restore] do |l|
         | 
| 104 | 
            +
                l =~ /^(\S+) \((.*?)\)$/ or raise "Can't read dump line: #{l.inspect}"
         | 
| 105 | 
            +
                mid, labels = $1, $2
         | 
| 106 | 
            +
                dump[mid] = labels.to_set_of_symbols
         | 
| 111 107 | 
             
              end
         | 
| 108 | 
            +
              puts "Read #{dump.size} entries from dump file."
         | 
| 109 | 
            +
              dump
         | 
| 110 | 
            +
            else
         | 
| 111 | 
            +
              {}
         | 
| 112 | 
            +
            end
         | 
| 112 113 |  | 
| 113 114 | 
             
            seen = {}
         | 
| 114 | 
            -
            index. | 
| 115 | 
            +
            index.lock_interactively or exit
         | 
| 115 116 | 
             
            begin
         | 
| 116 117 | 
             
              index.load
         | 
| 117 118 |  | 
| 118 | 
            -
              sources =  | 
| 119 | 
            -
                 | 
| 119 | 
            +
              sources = if opts[:all_sources]
         | 
| 120 | 
            +
                Redwood::SourceManager.sources
         | 
| 121 | 
            +
              elsif ARGV.empty?
         | 
| 122 | 
            +
                Redwood::SourceManager.usual_sources
         | 
| 123 | 
            +
              else
         | 
| 124 | 
            +
                ARGV.map do |uri|
         | 
| 125 | 
            +
                  Redwood::SourceManager.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
         | 
| 126 | 
            +
                end
         | 
| 120 127 | 
             
              end
         | 
| 121 | 
            -
              
         | 
| 122 | 
            -
              sources = index.usual_sources if sources.empty?
         | 
| 123 | 
            -
              sources = index.sources if opts[:all_sources]
         | 
| 124 128 |  | 
| 129 | 
            +
              ## for all target specifications except for only-new messages, reset the
         | 
| 130 | 
            +
              ## source to the beginning (or to the user-specified starting point.)
         | 
| 125 131 | 
             
              unless target == :new
         | 
| 126 132 | 
             
                if opts[:start_at]
         | 
| 127 133 | 
             
                  Trollop::die :start_at, "can only be used on one source" unless sources.size == 1
         | 
| @@ -131,57 +137,88 @@ begin | |
| 131 137 | 
             
                  sources.each { |s| s.reset! }
         | 
| 132 138 | 
             
                end
         | 
| 133 139 | 
             
              end
         | 
| 134 | 
            -
             | 
| 140 | 
            +
             | 
| 135 141 | 
             
              sources.each do |source|
         | 
| 136 | 
            -
                 | 
| 142 | 
            +
                puts "Scanning #{source}..."
         | 
| 137 143 | 
             
                num_added = num_updated = num_scanned = num_restored = 0
         | 
| 138 144 | 
             
                last_info_time = start_time = Time.now
         | 
| 139 145 |  | 
| 140 | 
            -
                Redwood::PollManager. | 
| 146 | 
            +
                Redwood::PollManager.each_message_from source do |m|
         | 
| 141 147 | 
             
                  num_scanned += 1
         | 
| 142 148 | 
             
                  seen[m.id] = true
         | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
             | 
| 146 | 
            -
             | 
| 147 | 
            -
                     | 
| 148 | 
            -
                     | 
| 149 | 
            -
                     | 
| 150 | 
            -
                     | 
| 149 | 
            +
                  old_m = index.build_message m.id
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  case target
         | 
| 152 | 
            +
                  when :changed
         | 
| 153 | 
            +
                    ## skip this message if we're operating only on changed messages, the
         | 
| 154 | 
            +
                    ## message is in the index, and it's unchanged from what the source is
         | 
| 155 | 
            +
                    ## reporting.
         | 
| 156 | 
            +
                    next if old_m && old_m.source.id == m.source.id && old_m.source_info == m.source_info
         | 
| 157 | 
            +
                  when :restored
         | 
| 158 | 
            +
                    ## skip if we're operating on restored messages, and this one
         | 
| 159 | 
            +
                    ## ain't (or we wouldn't be making a change)
         | 
| 160 | 
            +
                    next unless old_m && restored_state[m.id] && restored_state[m.id] != old_m.labels
         | 
| 161 | 
            +
                  when :new
         | 
| 162 | 
            +
                    ## nothing to do; we'll consider all messages starting at the start offset, which
         | 
| 163 | 
            +
                    ## hasn't been changed.
         | 
| 164 | 
            +
                  when :all
         | 
| 165 | 
            +
                    ## nothing to do; we'll consider all messages starting at the start offset, which
         | 
| 166 | 
            +
                    ## was reset to the beginning above.
         | 
| 151 167 | 
             
                  end
         | 
| 152 168 |  | 
| 153 | 
            -
                  ##  | 
| 154 | 
            -
                   | 
| 155 | 
            -
                   | 
| 156 | 
            -
                   | 
| 157 | 
            -
             | 
| 158 | 
            -
                  ##  | 
| 159 | 
            -
                   | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
                  ## m.labels is the default source labels. tweak these according
         | 
| 166 | 
            -
                  ## to default source state modification flags.
         | 
| 167 | 
            -
                  m.labels -= [:inbox] if opts[:archive]
         | 
| 168 | 
            -
                  m.labels -= [:unread] if opts[:read]
         | 
| 169 | 
            -
                  m.labels += opts[:extra_labels].strip.split(/\s*,\s*/).map { |x| x.intern } if opts[:extra_labels]
         | 
| 170 | 
            -
             | 
| 171 | 
            -
                  ## assign message labels based on the operation we're performing
         | 
| 172 | 
            -
                  case op
         | 
| 173 | 
            -
                  when :asis
         | 
| 174 | 
            -
                    m.labels = ((m.labels - [:unread, :inbox]) + index_state).uniq if index_state
         | 
| 175 | 
            -
                  when :restore
         | 
| 176 | 
            -
                    ## if the entry exists on disk
         | 
| 177 | 
            -
                    if restored_state[m.id]
         | 
| 178 | 
            -
                      m.labels = restored_state[m.id]
         | 
| 169 | 
            +
                  ## tweak source labels according to commandline arguments if necessary
         | 
| 170 | 
            +
                  m.labels.delete :inbox if opts[:archive]
         | 
| 171 | 
            +
                  m.labels.delete :unread if opts[:read]
         | 
| 172 | 
            +
                  m.labels += opts[:extra_labels].to_set_of_symbols(",")
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  ## decide what to do based on message labels and the operation we're performing
         | 
| 175 | 
            +
                  dothis, new_labels = case
         | 
| 176 | 
            +
                  when (op == :restore) && restored_state[m.id]
         | 
| 177 | 
            +
                    if old_m && (old_m.labels != restored_state[m.id])
         | 
| 178 | 
            +
                      num_restored += 1
         | 
| 179 | 
            +
                      [:update_message_state, restored_state[m.id]]
         | 
| 180 | 
            +
                    elsif old_m.nil?
         | 
| 179 181 | 
             
                      num_restored += 1
         | 
| 180 | 
            -
             | 
| 181 | 
            -
                       | 
| 182 | 
            +
                      m.labels = restored_state[m.id]
         | 
| 183 | 
            +
                      :add_message
         | 
| 184 | 
            +
                    else
         | 
| 185 | 
            +
                      # labels are the same; don't do anything
         | 
| 186 | 
            +
                    end
         | 
| 187 | 
            +
                  when op == :discard
         | 
| 188 | 
            +
                    if old_m && (old_m.labels != m.labels)
         | 
| 189 | 
            +
                      [:update_message_state, m.labels]
         | 
| 190 | 
            +
                    else
         | 
| 191 | 
            +
                      # labels are the same; don't do anything
         | 
| 192 | 
            +
                    end
         | 
| 193 | 
            +
                  else
         | 
| 194 | 
            +
                    ## duplicate behavior of poll mode: if index_state is non-nil, this is a newer
         | 
| 195 | 
            +
                    ## version of an older message, so merge in any new labels except :unread and
         | 
| 196 | 
            +
                    ## :inbox.
         | 
| 197 | 
            +
                    ##
         | 
| 198 | 
            +
                    ## TODO: refactor such that this isn't duplicated
         | 
| 199 | 
            +
                    if old_m
         | 
| 200 | 
            +
                      m.labels = old_m.labels + (m.labels - [:unread, :inbox])
         | 
| 201 | 
            +
                      :update_message
         | 
| 202 | 
            +
                    else
         | 
| 203 | 
            +
                      :add_message
         | 
| 182 204 | 
             
                    end
         | 
| 183 | 
            -
                   | 
| 184 | 
            -
             | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                  ## now, actually do the operation
         | 
| 208 | 
            +
                  case dothis
         | 
| 209 | 
            +
                  when :add_message
         | 
| 210 | 
            +
                    puts "Adding new message #{source}##{m.source_info} with labels #{m.labels}" if opts[:verbose]
         | 
| 211 | 
            +
                    index.add_message m unless opts[:dry_run]
         | 
| 212 | 
            +
                    num_added += 1
         | 
| 213 | 
            +
                  when :update_message
         | 
| 214 | 
            +
                    puts "Updating message #{source}##{m.source_info}; labels #{old_m.labels} => #{m.labels}; offset #{old_m.source_info} => #{m.source_info}" if opts[:verbose]
         | 
| 215 | 
            +
                    index.update_message m unless opts[:dry_run]
         | 
| 216 | 
            +
                    num_updated += 1
         | 
| 217 | 
            +
                  when :update_message_state
         | 
| 218 | 
            +
                    puts "Changing flags for #{source}##{m.source_info} from #{m.labels} to #{new_labels}" if opts[:verbose]
         | 
| 219 | 
            +
                    m.labels = new_labels
         | 
| 220 | 
            +
                    index.update_message_state m unless opts[:dry_run]
         | 
| 221 | 
            +
                    num_updated += 1
         | 
| 185 222 | 
             
                  end
         | 
| 186 223 |  | 
| 187 224 | 
             
                  if Time.now - last_info_time > PROGRESS_UPDATE_INTERVAL
         | 
| @@ -189,56 +226,40 @@ begin | |
| 189 226 | 
             
                    elapsed = last_info_time - start_time
         | 
| 190 227 | 
             
                    pctdone = source.respond_to?(:pct_done) ? source.pct_done : 100.0 * (source.cur_offset.to_f - source.start_offset).to_f / (source.end_offset - source.start_offset).to_f
         | 
| 191 228 | 
             
                    remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
         | 
| 192 | 
            -
                     | 
| 193 | 
            -
                  end
         | 
| 194 | 
            -
             | 
| 195 | 
            -
                  if index_state.nil?
         | 
| 196 | 
            -
                    puts "Adding message #{source}##{offset} from #{m.from} with state {#{m.labels * ', '}}" if opts[:verbose]
         | 
| 197 | 
            -
                    num_added += 1
         | 
| 198 | 
            -
                  else
         | 
| 199 | 
            -
                    puts "Updating message #{source}##{offset}, source #{entry[:source_id]} => #{source.id}, offset #{entry[:source_info]} => #{offset}, state {#{index_state * ', '}} => {#{m.labels * ', '}}" if opts[:verbose]
         | 
| 200 | 
            -
                    num_updated += 1
         | 
| 229 | 
            +
                    printf "## read %dm (about %.0f%%) @ %.1fm/s. %s elapsed, about %s remaining\n", num_scanned, pctdone, num_scanned / elapsed, elapsed.to_time_s, remaining.to_time_s
         | 
| 201 230 | 
             
                  end
         | 
| 202 | 
            -
             | 
| 203 | 
            -
                  opts[:dry_run] ? nil : m
         | 
| 204 231 | 
             
                end
         | 
| 205 | 
            -
             | 
| 206 | 
            -
                 | 
| 232 | 
            +
             | 
| 233 | 
            +
                puts "Scanned #{num_scanned}, added #{num_added}, updated #{num_updated} messages from #{source}."
         | 
| 234 | 
            +
                puts "Restored state on #{num_restored} (#{100.0 * num_restored / num_scanned}%) messages." if num_restored > 0
         | 
| 207 235 | 
             
              end
         | 
| 208 236 |  | 
| 209 237 | 
             
              ## delete any messages in the index that claim they're from one of
         | 
| 210 238 | 
             
              ## these sources, but that we didn't see.
         | 
| 211 | 
            -
               | 
| 212 | 
            -
             | 
| 213 | 
            -
              ## API.
         | 
| 214 | 
            -
              ##
         | 
| 215 | 
            -
              ## TODO: move this to Index, i suppose.
         | 
| 216 | 
            -
              if (target == :all || target == :changed) && !opts[:start_at]
         | 
| 217 | 
            -
                $stderr.puts "Deleting missing messages from the index..."
         | 
| 239 | 
            +
              if (target == :all || target == :changed)
         | 
| 240 | 
            +
                puts "Deleting missing messages from the index..."
         | 
| 218 241 | 
             
                num_del, num_scanned = 0, 0
         | 
| 219 242 | 
             
                sources.each do |source|
         | 
| 220 243 | 
             
                  raise "no source id for #{source}" unless source.id
         | 
| 221 | 
            -
                   | 
| 222 | 
            -
                  q += " +source_info: >= #{opts[:start_at]}" if opts[:start_at]
         | 
| 223 | 
            -
                  index.index.search_each(q, :limit => :all) do |docid, score|
         | 
| 244 | 
            +
                  index.each_message :source_id => source.id, :load_spam => true, :load_deleted => true, :load_killed => true do |m|
         | 
| 224 245 | 
             
                    num_scanned += 1
         | 
| 225 | 
            -
                     | 
| 226 | 
            -
             | 
| 227 | 
            -
                      puts "Deleting #{ | 
| 228 | 
            -
                      index. | 
| 246 | 
            +
                    unless seen[m.id]
         | 
| 247 | 
            +
                      next unless m.source_info >= opts[:start_at] if opts[:start_at]
         | 
| 248 | 
            +
                      puts "Deleting #{m.id}" if opts[:verbose]
         | 
| 249 | 
            +
                      index.delete m.id unless opts[:dry_run]
         | 
| 229 250 | 
             
                      num_del += 1
         | 
| 230 251 | 
             
                    end
         | 
| 231 252 | 
             
                  end
         | 
| 232 253 | 
             
                end
         | 
| 233 | 
            -
                 | 
| 254 | 
            +
                puts "Deleted #{num_del} / #{num_scanned} messages"
         | 
| 234 255 | 
             
              end
         | 
| 235 256 |  | 
| 236 257 | 
             
              index.save
         | 
| 237 258 |  | 
| 238 259 | 
             
              if opts[:optimize]
         | 
| 239 | 
            -
                 | 
| 240 | 
            -
                optt = time { index. | 
| 241 | 
            -
                 | 
| 260 | 
            +
                puts "Optimizing index..."
         | 
| 261 | 
            +
                optt = time { index.optimize unless opts[:dry_run] }
         | 
| 262 | 
            +
                puts "Optimized index of size #{index.size} in #{optt}s."
         | 
| 242 263 | 
             
              end
         | 
| 243 264 | 
             
            rescue Redwood::FatalSourceError => e
         | 
| 244 265 | 
             
              $stderr.puts "Sorry, I couldn't communicate with a source: #{e.message}"
         | 
    
        data/bin/sup-sync-back
    CHANGED
    
    | @@ -4,6 +4,7 @@ require 'rubygems' | |
| 4 4 | 
             
            require 'uri'
         | 
| 5 5 | 
             
            require 'tempfile'
         | 
| 6 6 | 
             
            require 'trollop'
         | 
| 7 | 
            +
            require 'enumerator'
         | 
| 7 8 | 
             
            require "sup"
         | 
| 8 9 |  | 
| 9 10 | 
             
            ## save a message 'm' to an open file pointer 'fp'
         | 
| @@ -14,6 +15,10 @@ def die msg | |
| 14 15 | 
             
              $stderr.puts "Error: #{msg}"
         | 
| 15 16 | 
             
              exit(-1)
         | 
| 16 17 | 
             
            end
         | 
| 18 | 
            +
            def has_any_from_source_with_label? index, source, label
         | 
| 19 | 
            +
              query = { :source_id => source.id, :label => label, :limit => 1, :load_spam => true, :load_deleted => true, :load_killed => true }
         | 
| 20 | 
            +
              not Enumerable::Enumerator.new(index, :each_id, query).map.empty?
         | 
| 21 | 
            +
            end
         | 
| 17 22 |  | 
| 18 23 | 
             
            opts = Trollop::options do
         | 
| 19 24 | 
             
              version "sup-sync-back (sup #{Redwood::VERSION})"
         | 
| @@ -60,8 +65,8 @@ EOS | |
| 60 65 | 
             
            end
         | 
| 61 66 |  | 
| 62 67 | 
             
            Redwood::start
         | 
| 63 | 
            -
            index = Redwood::Index. | 
| 64 | 
            -
            index. | 
| 68 | 
            +
            index = Redwood::Index.init
         | 
| 69 | 
            +
            index.lock_interactively or exit
         | 
| 65 70 |  | 
| 66 71 | 
             
            deleted_fp, spam_fp = nil
         | 
| 67 72 | 
             
            unless opts[:dry_run]
         | 
| @@ -75,13 +80,13 @@ begin | |
| 75 80 | 
             
              index.load
         | 
| 76 81 |  | 
| 77 82 | 
             
              sources = ARGV.map do |uri|
         | 
| 78 | 
            -
                s =  | 
| 83 | 
            +
                s = Redwood::SourceManager.source_for(uri) or die "unknown source: #{uri}. Did you add it with sup-add first?"
         | 
| 79 84 | 
             
                s.is_a?(Redwood::MBox::Loader) or die "#{uri} is not an mbox source."
         | 
| 80 85 | 
             
                s
         | 
| 81 86 | 
             
              end
         | 
| 82 87 |  | 
| 83 88 | 
             
              if sources.empty?
         | 
| 84 | 
            -
                sources =  | 
| 89 | 
            +
                sources = Redwood::SourceManager.usual_sources.select { |s| s.is_a? Redwood::MBox::Loader }
         | 
| 85 90 | 
             
              end
         | 
| 86 91 |  | 
| 87 92 | 
             
              unless sources.all? { |s| s.file_path.nil? } || File.executable?(dotlockfile) || opts[:dont_use_dotlockfile]
         | 
| @@ -96,37 +101,37 @@ EOS | |
| 96 101 | 
             
              sources.each do |source|
         | 
| 97 102 | 
             
                $stderr.puts "Scanning #{source}..."
         | 
| 98 103 |  | 
| 99 | 
            -
                unless ((opts[:drop_deleted] || opts[:move_deleted]) &&  | 
| 104 | 
            +
                unless ((opts[:drop_deleted] || opts[:move_deleted]) && has_any_from_source_with_label?(index, source, :deleted)) || ((opts[:drop_spam] || opts[:move_spam]) && has_any_from_source_with_label?(index, source, :spam))
         | 
| 100 105 | 
             
                  $stderr.puts "Nothing to do from this source; skipping"
         | 
| 101 106 | 
             
                  next
         | 
| 102 107 | 
             
                end
         | 
| 103 108 |  | 
| 104 109 | 
             
                source.reset!
         | 
| 105 110 | 
             
                num_dropped = num_moved = num_scanned = 0
         | 
| 106 | 
            -
             | 
| 111 | 
            +
             | 
| 107 112 | 
             
                out_fp = Tempfile.new "sup-sync-back-#{source.id}"
         | 
| 108 | 
            -
                Redwood::PollManager. | 
| 113 | 
            +
                Redwood::PollManager.each_message_from source do |m|
         | 
| 109 114 | 
             
                  num_scanned += 1
         | 
| 110 115 |  | 
| 111 | 
            -
                  if  | 
| 112 | 
            -
                    labels =  | 
| 116 | 
            +
                  if(m_old = index.build_message(m.id))
         | 
| 117 | 
            +
                    labels = m_old.labels
         | 
| 113 118 |  | 
| 114 119 | 
             
                    if labels.member? :deleted
         | 
| 115 120 | 
             
                      if opts[:drop_deleted]
         | 
| 116 | 
            -
                        puts "Dropping deleted message #{source}##{ | 
| 121 | 
            +
                        puts "Dropping deleted message #{source}##{m.source_info}" if opts[:verbose]
         | 
| 117 122 | 
             
                        num_dropped += 1
         | 
| 118 123 | 
             
                      elsif opts[:move_deleted] && labels.member?(:deleted)
         | 
| 119 | 
            -
                        puts "Moving deleted message #{source}##{ | 
| 124 | 
            +
                        puts "Moving deleted message #{source}##{m.source_info}" if opts[:verbose]
         | 
| 120 125 | 
             
                        save m, deleted_fp unless opts[:dry_run]
         | 
| 121 126 | 
             
                        num_moved += 1
         | 
| 122 127 | 
             
                      end
         | 
| 123 128 |  | 
| 124 129 | 
             
                    elsif labels.member? :spam
         | 
| 125 130 | 
             
                      if opts[:drop_spam]
         | 
| 126 | 
            -
                        puts "Dropping spam message #{source}##{ | 
| 131 | 
            +
                        puts "Dropping spam message #{source}##{m.source_info}" if opts[:verbose]
         | 
| 127 132 | 
             
                        num_dropped += 1
         | 
| 128 133 | 
             
                      elsif opts[:move_spam] && labels.member?(:spam)
         | 
| 129 | 
            -
                        puts "Moving spam message #{source}##{ | 
| 134 | 
            +
                        puts "Moving spam message #{source}##{m.source_info}" if opts[:verbose]
         | 
| 130 135 | 
             
                        save m, spam_fp unless opts[:dry_run]
         | 
| 131 136 | 
             
                        num_moved += 1
         | 
| 132 137 | 
             
                      end
         | 
| @@ -136,8 +141,6 @@ EOS | |
| 136 141 | 
             
                  else
         | 
| 137 142 | 
             
                    save m, out_fp unless opts[:dry_run]
         | 
| 138 143 | 
             
                  end
         | 
| 139 | 
            -
             | 
| 140 | 
            -
                  nil # don't actually add anything!
         | 
| 141 144 | 
             
                end
         | 
| 142 145 | 
             
                $stderr.puts "Scanned #{num_scanned}, dropped #{num_dropped}, moved #{num_moved} messages from #{source}."
         | 
| 143 146 | 
             
                modified_sources << source if num_dropped > 0 || num_moved > 0
         |