]> git.cworth.org Git - sup/commitdiff
maintain labels as Sets rather than arrays
authorWilliam Morgan <wmorgan-sup@masanjin.net>
Tue, 11 Aug 2009 20:00:52 +0000 (16:00 -0400)
committerWilliam Morgan <wmorgan-sup@masanjin.net>
Mon, 17 Aug 2009 18:41:22 +0000 (14:41 -0400)
16 files changed:
bin/sup-dump
bin/sup-sync
bin/sup-tweak-labels
lib/sup/buffer.rb
lib/sup/ferret_index.rb
lib/sup/imap.rb
lib/sup/label.rb
lib/sup/maildir.rb
lib/sup/mbox/loader.rb
lib/sup/message.rb
lib/sup/modes/inbox-mode.rb
lib/sup/modes/thread-index-mode.rb
lib/sup/poll.rb
lib/sup/thread.rb
lib/sup/util.rb
lib/sup/xapian_index.rb

index ba36b21adb0f102342f370d417640785541890bb..992fd0bb74cd0aa7da29729ae38b460d0aad8bc8 100755 (executable)
@@ -26,5 +26,5 @@ Redwood::SourceManager.new
 index.load
 
 index.each_message :load_spam => true, :load_deleted => true, :load_killed => true do |m|
-  puts "#{m.id} (#{m.labels * ' '})"
+  puts "#{m.id} (#{m.labels.to_a * ' '})"
 end
index 44ff3b20a873f01dc33fde8c7b6345521cc84fb9..f233072ffc4810dc613b983f2c9bc05bc330141f 100755 (executable)
@@ -102,7 +102,7 @@ restored_state =
     IO.foreach opts[:restore] do |l|
       l =~ /^(\S+) \((.*?)\)$/ or raise "Can't read dump line: #{l.inspect}"
       mid, labels = $1, $2
-      dump[mid] = labels.symbolistize
+      dump[mid] = labels.to_set_of_symbols
     end
     $stderr.puts "Read #{dump.size} entries from dump file."
     dump
@@ -159,19 +159,22 @@ begin
       index_state = m_old.labels.dup if m_old
 
       ## skip if we're operating on restored messages, and this one
-      ## ain't.
-      next if target == :restored && (!restored_state[m.id] || (index_state && restored_state[m.id].sort_by { |s| s.to_s } == index_state.sort_by { |s| s.to_s }))
+      ## ain't (or we wouldn't be making a change)
+      next if target == :restored && (!restored_state[m.id] || !index_state || restored_state[m.id] == index_state)
 
       ## m.labels is the default source labels. tweak these according
       ## to default source state modification flags.
-      m.labels -= [:inbox] if opts[:archive]
-      m.labels -= [:unread] if opts[:read]
-      m.labels += opts[:extra_labels].strip.split(/\s*,\s*/).map { |x| x.intern } if opts[:extra_labels]
+      m.labels.delete :inbox if opts[:archive]
+      m.labels.delete :unread if opts[:read]
+      m.labels += opts[:extra_labels].to_set_of_symbols(",") if opts[:extra_labels]
 
       ## assign message labels based on the operation we're performing
       case op
       when :asis
-        m.labels = ((m.labels - [:unread, :inbox]) + index_state).uniq if index_state
+        ## duplicate behavior of poll mode: if index_state is non-nil, this is a newer
+        ## version of an older message, so merge in any new labels except :unread and
+        ## :inbox.
+        m.labels = ((m.labels - [:unread, :inbox]) + index_state) if index_state
       when :restore
         ## if the entry exists on disk
         if restored_state[m.id]
@@ -193,10 +196,10 @@ begin
       end
 
       if index_state.nil?
-        puts "Adding message #{source}##{offset} from #{m.from} with state {#{m.labels * ', '}}" if opts[:verbose]
+        puts "Adding message #{source}##{offset} from #{m.from} with state {#{m.labels.to_a * ', '}}" if opts[:verbose]
         num_added += 1
       else
-        puts "Updating message #{source}##{offset}, source #{m_old.source.id} => #{source.id}, offset #{m_old.source_info} => #{offset}, state {#{index_state * ', '}} => {#{m.labels * ', '}}" if opts[:verbose]
+        puts "Updating message #{source}##{offset}, source #{m_old.source.id} => #{source.id}, offset #{m_old.source_info} => #{offset}, state {#{index_state.to_a * ', '}} => {#{m.labels.to_a * ', '}}" if opts[:verbose]
         num_updated += 1
       end
 
index 8ae5c26f9054c841ad612bdaf29c79f300cab753..905aac2540fddbc53b748c845b2c6b93ad657731 100755 (executable)
@@ -54,8 +54,8 @@ EOS
 end
 opts[:verbose] = true if opts[:very_verbose]
 
-add_labels = (opts[:add] || "").split(",").map { |l| l.intern }.uniq
-remove_labels = (opts[:remove] || "").split(",").map { |l| l.intern }.uniq
+add_labels = opts[:add].to_set_of_symbols ","
+remove_labels = opts[:remove].to_set_of_symbols ","
 
 Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
 
@@ -95,16 +95,15 @@ begin
     num_scanned += 1
 
     m = index.build_message id
-    old_labels = m.labels.clone
+    old_labels = m.labels.dup
 
     m.labels += add_labels
     m.labels -= remove_labels
-    m.labels = m.labels.uniq
 
-    unless m.labels.sort_by { |s| s.to_s } == old_labels.sort_by { |s| s.to_s }
+    unless m.labels == old_labels
       num_changed += 1
       puts "From #{m.from}, subject: #{m.subj}" if opts[:very_verbose]
-      puts "#{m.id}: {#{old_labels.join ','}} => {#{m.labels.join ','}}" if opts[:verbose]
+      puts "#{m.id}: {#{old_labels.to_a.join ','}} => {#{m.labels.to_a.join ','}}" if opts[:verbose]
       puts if opts[:very_verbose]
       index.sync_message m unless opts[:dry_run]
     end
index 5f52d1dff53ab62f80c008a7a25b6a84505e308c..3266473a5c1b0e3bd1a14d5dbebf45244814ac35 100644 (file)
@@ -495,7 +495,7 @@ EOS
 
     return unless answer
 
-    user_labels = answer.symbolistize
+    user_labels = answer.to_set_of_symbols
     user_labels.each do |l|
       if forbidden_labels.include?(l) || LabelManager::RESERVED_LABELS.include?(l)
         BufferManager.flash "'#{l}' is a reserved label!"
index f3d414770212e090d0ce03ff9b58a5a07df45233..546faf8449b8b77fe3ada5c95c23ac82ca206a3f 100644 (file)
@@ -79,13 +79,13 @@ class FerretIndex < BaseIndex
     ## written in this manner to support previous versions of the index which
     ## did not keep around the entry body. upgrading is thus seamless.
     entry ||= {}
-    labels = m.labels.uniq # override because this is the new state, unless...
+    labels = m.labels # override because this is the new state, unless...
 
     ## if we are a later version of a message, ignore what's in the index,
     ## but merge in the labels.
     if entry[:source_id] && entry[:source_info] && entry[:label] &&
       ((entry[:source_id].to_i > source_id) || (entry[:source_info].to_i < m.source_info))
-      labels = (entry[:label].symbolistize + m.labels).uniq
+      labels += entry[:label].to_set_of_symbols
       #Redwood::log "found updated version of message #{m.id}: #{m.subj}"
       #Redwood::log "previous version was at #{entry[:source_id].inspect}:#{entry[:source_info].inspect}, this version at #{source_id.inspect}:#{m.source_info.inspect}"
       #Redwood::log "merged labels are #{labels.inspect} (index #{entry[:label].inspect}, message #{m.labels.inspect})"
@@ -103,7 +103,7 @@ class FerretIndex < BaseIndex
       :date => (entry[:date] || m.date.to_indexable_s),
       :body => (entry[:body] || m.indexable_content),
       :snippet => snippet, # always override
-      :label => labels.uniq.join(" "),
+      :label => labels.to_a.join(" "),
       :attachments => (entry[:attachments] || m.attachments.uniq.join(" ")),
 
       ## always override :from and :to.
@@ -259,7 +259,7 @@ class FerretIndex < BaseIndex
       }
 
       m = Message.new :source => source, :source_info => doc[:source_info].to_i,
-                  :labels => doc[:label].symbolistize,
+                  :labels => doc[:label].to_set_of_symbols,
                   :snippet => doc[:snippet]
       m.parse_header fake_header
       m
index 6c04d885d5856a719ba46460b465f51186034a3e..14acdb775764655152a0062f3010dc7b9d38f7b9 100644 (file)
@@ -4,6 +4,7 @@ require 'stringio'
 require 'time'
 require 'rmail'
 require 'cgi'
+require 'set'
 
 ## TODO: remove synchronized method protector calls; use a Monitor instead
 ## (ruby's reentrant mutex)
@@ -69,7 +70,7 @@ class IMAP < Source
     @imap_state = {}
     @ids = []
     @last_scan = nil
-    @labels = ((labels || []) - LabelManager::RESERVED_LABELS).uniq.freeze
+    @labels = Set.new((labels || []) - LabelManager::RESERVED_LABELS)
     @say_id = nil
     @mutex = Mutex.new
   end
index 47d632baafaa58e2dfa10111172b64e08b9f9f55..3e7bacbd795b2c1b9a3d49754d6f41878608f57a 100644 (file)
@@ -61,9 +61,9 @@ class LabelManager
       l
     end
   end
-  
+
   def << t
-    t = t.intern unless t.is_a? Symbol
+    raise ArgumentError, "expecting a symbol" unless t.is_a? Symbol
     unless @labels.member?(t) || RESERVED_LABELS.member?(t)
       @labels[t] = true
       @new_labels[t] = true
index c6577c17d48274a5abdc17367392245dd1b4dd01..80903e28ca6cd6231df0563dae4977c0aa5cfe34 100644 (file)
@@ -23,7 +23,7 @@ class Maildir < Source
     raise ArgumentError, "maildir URI must have a path component" unless uri.path
 
     @dir = uri.path
-    @labels = (labels || []).freeze
+    @labels = Set.new(labels || [])
     @ids = []
     @ids_to_fns = {}
     @last_scan = nil
index ea277cf0f80ffa41397484a92a6940cb468dd0a2..f35c7df8f37b489fb38ebb8827e2d9e50a1985de 100644 (file)
@@ -1,17 +1,17 @@
 require 'rmail'
 require 'uri'
+require 'set'
 
 module Redwood
 module MBox
 
 class Loader < Source
   yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels
-  attr_accessor :labels
 
   ## uri_or_fp is horrific. need to refactor.
-  def initialize uri_or_fp, start_offset=0, usual=true, archived=false, id=nil, labels=[]
+  def initialize uri_or_fp, start_offset=0, usual=true, archived=false, id=nil, labels=nil
     @mutex = Mutex.new
-    @labels = ((labels || []) - LabelManager::RESERVED_LABELS).uniq.freeze
+    @labels = Set.new((labels || []) - LabelManager::RESERVED_LABELS)
 
     case uri_or_fp
     when String
@@ -168,7 +168,7 @@ class Loader < Source
     end
 
     self.cur_offset = next_offset
-    [returned_offset, (self.labels + [:unread]).uniq]
+    [returned_offset, (@labels + [:unread])]
   end
 end
 
index 7c6374651e379ea0947caf7072390bba6d981eb3..3b10744dfb2b0af752581a75caaaa858efe54e00 100644 (file)
@@ -46,7 +46,7 @@ class Message
     @snippet = opts[:snippet]
     @snippet_contains_encrypted_content = false
     @have_snippet = !(opts[:snippet].nil? || opts[:snippet].empty?)
-    @labels = (opts[:labels] || []).to_set_of_symbols
+    @labels = Set.new(opts[:labels] || [])
     @dirty = false
     @encrypted = false
     @chunks = nil
@@ -165,14 +165,14 @@ class Message
   end
 
   def has_label? t; @labels.member? t; end
-  def add_label t
-    return if @labels.member? t
-    @labels = (@labels + [t]).to_set_of_symbols
+  def add_label l
+    return if @labels.member? l
+    @labels << l
     @dirty = true
   end
-  def remove_label t
-    return unless @labels.member? t
-    @labels.delete t
+  def remove_label l
+    return unless @labels.member? l
+    @labels.delete l
     @dirty = true
   end
 
@@ -181,7 +181,9 @@ class Message
   end
 
   def labels= l
-    @labels = l.to_set_of_symbols
+    raise ArgumentError, "not a set" unless l.is_a?(Set)
+    return if @labels == l
+    @labels = l
     @dirty = true
   end
 
index d8daeb9c4cbe19ad1fc2644aa1d5c84a4e382f23..ba095dab7c09696f60efb7e68c993d614da9e7b8 100644 (file)
@@ -1,4 +1,4 @@
-require 'thread'
+require 'sup'
 
 module Redwood
 
@@ -15,9 +15,7 @@ class InboxMode < ThreadIndexMode
     @@instance = self
   end
 
-  def is_relevant? m
-    m.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty?
-  end
+  def is_relevant? m; (m.labels & [:spam, :deleted, :killed, :inbox]) == Set.new([:inbox]) end
 
   ## label-list-mode wants to be able to raise us if the user selects
   ## the "inbox" label, so we need to keep our singletonness around
index b6711199e5269e6e6c67fb206c967ea44b63b044..905ad982034155d7e582b6a08239a63bd440ecb2 100644 (file)
@@ -533,9 +533,9 @@ EOS
     keepl, modifyl = thread.labels.partition { |t| speciall.member? t }
 
     user_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", modifyl, @hidden_labels
-
     return unless user_labels
-    thread.labels = keepl + user_labels
+
+    thread.labels = Set.new(keepl) + user_labels
     user_labels.each { |l| LabelManager << l }
     update_text_for_line curpos
 
index 8a9d2188b7ec8f5f92e5bd44fc2beb94d475c282..0c46d2f4d1d3e67ddd33ad219857835963e293c3 100644 (file)
@@ -97,14 +97,14 @@ EOS
         numi = 0
         add_messages_from source do |m_old, m, offset|
           ## always preserve the labels on disk.
-          m.labels = ((m.labels - [:unread, :inbox]) + m_old.labels).uniq if m_old
-          yield "Found message at #{offset} with labels {#{m.labels * ', '}}"
+          m.labels = (m.labels - [:unread, :inbox]) + m_old.labels if m_old
+          yield "Found message at #{offset} with labels {#{m.labels.to_a * ', '}}"
           unless m_old
             num += 1
             from_and_subj << [m.from && m.from.longname, m.subj]
-            if m.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty?
+            if (m.labels & [:inbox, :spam, :deleted, :killed]) == Set.new([:inbox])
               from_and_subj_inbox << [m.from && m.from.longname, m.subj]
-              numi += 1 
+              numi += 1
             end
           end
           m
index d395c3588f7274448dc86d2d784ee9021b29c3b0..1474b6e5c34a66b663da29562e9b430be80684a1 100644 (file)
@@ -24,6 +24,8 @@
 ## a faked root object tying them all together into one tree
 ## structure.
 
+require 'set'
+
 module Redwood
 
 class Thread
@@ -123,11 +125,10 @@ class Thread
 
   def size; map { |m, *o| m ? 1 : 0 }.sum; end
   def subj; argfind { |m, *o| m && m.subj }; end
-  def labels
-      map { |m, *o| m && m.labels }.flatten.compact.uniq.sort_by { |t| t.to_s }
-  end
+  def labels; inject(Set.new) { |s, (m, *o)| m ? s | m.labels : s } end
   def labels= l
-    each { |m, *o| m && m.labels = l.clone }
+    raise ArgumentError, "not a set" unless l.is_a?(Set)
+    each { |m, *o| m && m.labels = l.dup }
   end
 
   def latest_message
index 3f2c901a160823c4229d922fd1c052fa42e6a428..c3cc4b223b059aa5854d2161526423dbfd315d5b 100644 (file)
@@ -2,6 +2,7 @@ require 'thread'
 require 'lockfile'
 require 'mime/types'
 require 'pathname'
+require 'set'
 
 ## time for some monkeypatching!
 class Lockfile
@@ -288,10 +289,12 @@ class String
     end
   end
 
-  ## takes a space-separated list of words, and returns an array of symbols.
-  ## typically used in Sup for translating Ferret's representation of a list
-  ## of labels (a string) to an array of label symbols.
-  def symbolistize; split.map { |x| x.intern } end
+  ## takes a list of words, and returns an array of symbols.  typically used in
+  ## Sup for translating Ferret's representation of a list of labels (a string)
+  ## to an array of label symbols.
+  ##
+  ## split_on will be passed to String#split, so you can leave this nil for space.
+  def to_set_of_symbols split_on=nil; Set.new split(split_on).map { |x| x.strip.intern } end
 end
 
 class Numeric
@@ -419,10 +422,6 @@ class Array
 
   def last= e; self[-1] = e end
   def nonempty?; !empty? end
-
-  def to_set_of_symbols
-    map { |x| x.is_a?(Symbol) ? x : x.intern }.uniq
-  end
 end
 
 class Time
index 861c2a3a7fc1618343da4f8ac51d62e36bd4993f..33f220410b38aca0ad0362668e54c36642cf43d1 100644 (file)
@@ -100,7 +100,7 @@ class XapianIndex < BaseIndex
       :source_info => m.source_info,
       :date => (entry[:date] || m.date),
       :snippet => snippet,
-      :labels => labels.uniq,
+      :labels => labels,
       :from => (entry[:from] || [m.from.email, m.from.name]),
       :to => (entry[:to] || m.to.map { |p| [p.email, p.name] }),
       :cc => (entry[:cc] || m.cc.map { |p| [p.email, p.name] }),
@@ -110,7 +110,7 @@ class XapianIndex < BaseIndex
       :replytos => (entry[:replytos] || m.replytos),
     }
 
-    m.labels.each { |l| LabelManager << l }
+    labels.each { |l| LabelManager << l }
 
     synchronize do
       index_message m, opts