Class: Redwood::ThreadSet
Overview
A set of threads, so a forest. Is integrated with the index and builds thread structures by reading messages from it.
If ‘thread_by_subj’ is true, puts messages with the same subject in one thread, even if they don’t reference each other. This is helpful for crappy MUAs that don’t set In-reply-to: or References: headers, but means that messages may be threaded unnecessarily.
The following invariants are maintained: every Thread has at least one Container tree, and every Container tree has at least one Message.
Instance Attribute Summary collapse
-
#num_messages ⇒ Object
readonly
Returns the value of attribute num_messages.
Instance Method Summary collapse
-
#add_message(message) ⇒ Object
the heart of the threading code.
-
#add_thread(t) ⇒ Object
merges in a pre-loaded thread.
- #contains?(m) ⇒ Boolean
- #contains_id?(id) ⇒ Boolean
- #dump(f = $stdout) ⇒ Object
-
#initialize(index, thread_by_subj = true) ⇒ ThreadSet
constructor
A new instance of ThreadSet.
- #is_relevant?(m) ⇒ Boolean
-
#join_threads(threads) ⇒ Object
merges two threads together.
-
#load_n_threads(num, opts = {}) ⇒ Object
load in (at most) num number of threads from the index.
-
#load_thread_for_message(m, opts = {}) ⇒ Object
loads in all messages needed to thread m may do nothing if m’s thread is killed.
- #remove_id(mid) ⇒ Object
- #remove_thread_containing_id(mid) ⇒ Object
- #size ⇒ Object
- #thread_for(m) ⇒ Object
- #thread_for_id(mid) ⇒ Object
- #threads ⇒ Object
Constructor Details
#initialize(index, thread_by_subj = true) ⇒ ThreadSet
Returns a new instance of ThreadSet.
258 259 260 261 262 263 264 265 266 |
# File 'lib/sup/thread.rb', line 258 def initialize index, thread_by_subj=true @index = index @num_messages = 0 ## map from message ids to container objects @messages = SavingHash.new { |id| Container.new id } ## map from subject strings or (or root message ids) to thread objects @threads = SavingHash.new { Thread.new } @thread_by_subj = thread_by_subj end |
Instance Attribute Details
#num_messages ⇒ Object (readonly)
Returns the value of attribute num_messages.
255 256 257 |
# File 'lib/sup/thread.rb', line 255 def @num_messages end |
Instance Method Details
#add_message(message) ⇒ Object
the heart of the threading code
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
# File 'lib/sup/thread.rb', line 391 def el = @messages[.id] return if el. # we've seen it before #puts "adding: #{message.id}, refs #{message.refs.inspect}" el. = oldroot = el.root ## link via references: (.refs + [el.id]).inject(nil) do |prev, ref_id| ref = @messages[ref_id] link prev, ref if prev ref end ## link via in-reply-to: .replytos.each do |ref_id| ref = @messages[ref_id] link ref, el, true break # only do the first one end root = el.root key = if thread_by_subj? Message.normalize_subj root.subj else root.id end ## check to see if the subject is still the same (in the case ## that we first added a child message with a different ## subject) if root.thread if @threads.member?(key) && @threads[key] != root.thread @threads.delete key end else thread = @threads[key] thread << root root.thread = thread end ## last bit @num_messages += 1 end |
#add_thread(t) ⇒ Object
merges in a pre-loaded thread
355 356 357 358 |
# File 'lib/sup/thread.rb', line 355 def add_thread t raise "duplicate" if @threads.values.member? t t.each { |m, *o| m } end |
#contains?(m) ⇒ Boolean
271 |
# File 'lib/sup/thread.rb', line 271 def contains? m; contains_id? m.id end |
#contains_id?(id) ⇒ Boolean
269 |
# File 'lib/sup/thread.rb', line 269 def contains_id? id; @messages.member?(id) && !@messages[id].empty? end |
#dump(f = $stdout) ⇒ Object
276 277 278 279 280 281 282 283 |
# File 'lib/sup/thread.rb', line 276 def dump f=$stdout @threads.each do |s, t| f.puts "**********************" f.puts "** for subject #{s} **" f.puts "**********************" t.dump f end end |
#is_relevant?(m) ⇒ Boolean
386 387 388 |
# File 'lib/sup/thread.rb', line 386 def is_relevant? m m.refs.any? { |ref_id| @messages.member? ref_id } end |
#join_threads(threads) ⇒ Object
merges two threads together. both must be members of this threadset. does its best, heuristically, to determine which is the parent.
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
# File 'lib/sup/thread.rb', line 362 def join_threads threads return if threads.size < 2 containers = threads.map do |t| c = @messages.member?(t.first.id) ? @messages[t.first.id] : nil raise "not in threadset: #{t.first.id}" unless c && c. c end ## use subject headers heuristically parent = containers.find { |c| !c.is_reply? } ## no thread was rooted by a non-reply, so make a fake parent parent ||= @messages["joining-ref-" + containers.map { |c| c.id }.join("-")] containers.each do |c| next if c == parent c..add_ref parent.id link parent, c end true end |
#load_n_threads(num, opts = {}) ⇒ Object
load in (at most) num number of threads from the index
333 334 335 336 337 338 339 340 341 342 |
# File 'lib/sup/thread.rb', line 333 def load_n_threads num, opts={} @index.each_id_by_date opts do |mid, builder| break if size >= num unless num == -1 next if contains_id? mid m = builder.call m, :skip_killed => opts[:skip_killed], :load_deleted => opts[:load_deleted], :load_spam => opts[:load_spam] yield size if block_given? end end |
#load_thread_for_message(m, opts = {}) ⇒ Object
loads in all messages needed to thread m may do nothing if m’s thread is killed
346 347 348 349 350 351 352 |
# File 'lib/sup/thread.rb', line 346 def m, opts={} good = @index. m, opts do |mid, builder| next if contains_id? mid builder.call end m if good end |
#remove_id(mid) ⇒ Object
318 319 320 321 322 323 |
# File 'lib/sup/thread.rb', line 318 def remove_id mid return unless @messages.member?(mid) c = @messages[mid] remove_container c prune_thread_of c end |
#remove_thread_containing_id(mid) ⇒ Object
325 326 327 328 329 330 |
# File 'lib/sup/thread.rb', line 325 def remove_thread_containing_id mid return unless @messages.member?(mid) c = @messages[mid] t = c.root.thread @threads.delete_if { |key, thread| t == thread } end |
#size ⇒ Object
274 |
# File 'lib/sup/thread.rb', line 274 def size; @threads.size end |
#thread_for(m) ⇒ Object
270 |
# File 'lib/sup/thread.rb', line 270 def thread_for m; thread_for_id m.id end |
#thread_for_id(mid) ⇒ Object
268 |
# File 'lib/sup/thread.rb', line 268 def thread_for_id mid; @messages.member?(mid) && @messages[mid].root.thread end |
#threads ⇒ Object
273 |
# File 'lib/sup/thread.rb', line 273 def threads; @threads.values end |