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
- #delete_message(message) ⇒ Object
- #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.
262 263 264 265 266 267 268 269 270 |
# File 'lib/sup/thread.rb', line 262 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.
259 260 261 |
# File 'lib/sup/thread.rb', line 259 def @num_messages end |
Instance Method Details
#add_message(message) ⇒ Object
the heart of the threading code
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 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/sup/thread.rb', line 401 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
359 360 361 362 |
# File 'lib/sup/thread.rb', line 359 def add_thread t raise "duplicate" if @threads.values.member? t t.each { |m, *o| m } end |
#contains?(m) ⇒ Boolean
275 |
# File 'lib/sup/thread.rb', line 275 def contains? m; contains_id? m.id end |
#contains_id?(id) ⇒ Boolean
273 |
# File 'lib/sup/thread.rb', line 273 def contains_id? id; @messages.member?(id) && !@messages[id].empty? end |
#delete_message(message) ⇒ Object
394 395 396 397 398 |
# File 'lib/sup/thread.rb', line 394 def el = @messages[.id] return unless el. el. = nil end |
#dump(f = $stdout) ⇒ Object
280 281 282 283 284 285 286 287 |
# File 'lib/sup/thread.rb', line 280 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
390 391 392 |
# File 'lib/sup/thread.rb', line 390 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.
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/sup/thread.rb', line 366 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
337 338 339 340 341 342 343 344 345 346 |
# File 'lib/sup/thread.rb', line 337 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
350 351 352 353 354 355 356 |
# File 'lib/sup/thread.rb', line 350 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
322 323 324 325 326 327 |
# File 'lib/sup/thread.rb', line 322 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
329 330 331 332 333 334 |
# File 'lib/sup/thread.rb', line 329 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
278 |
# File 'lib/sup/thread.rb', line 278 def size; @threads.size end |
#thread_for(m) ⇒ Object
274 |
# File 'lib/sup/thread.rb', line 274 def thread_for m; thread_for_id m.id end |
#thread_for_id(mid) ⇒ Object
272 |
# File 'lib/sup/thread.rb', line 272 def thread_for_id mid; @messages.member?(mid) && @messages[mid].root.thread end |
#threads ⇒ Object
277 |
# File 'lib/sup/thread.rb', line 277 def threads; @threads.values end |