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
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
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]
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
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?
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
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!"
## 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})"
: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.
}
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
require 'time'
require 'rmail'
require 'cgi'
+require 'set'
## TODO: remove synchronized method protector calls; use a Monitor instead
## (ruby's reentrant mutex)
@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
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
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
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
end
self.cur_offset = next_offset
- [returned_offset, (self.labels + [:unread]).uniq]
+ [returned_offset, (@labels + [:unread])]
end
end
@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
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
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
-require 'thread'
+require 'sup'
module Redwood
@@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
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
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
## a faked root object tying them all together into one tree
## structure.
+require 'set'
+
module Redwood
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
require 'lockfile'
require 'mime/types'
require 'pathname'
+require 'set'
## time for some monkeypatching!
class Lockfile
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
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
: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] }),
:replytos => (entry[:replytos] || m.replytos),
}
- m.labels.each { |l| LabelManager << l }
+ labels.each { |l| LabelManager << l }
synchronize do
index_message m, opts