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.
264 265 266 267 268 269 270 271 272 |
# File 'lib/sup/thread.rb', line 264 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.
261 262 263 |
# File 'lib/sup/thread.rb', line 261 def @num_messages end |
Instance Method Details
#add_message(message) ⇒ Object
the heart of the threading code
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 448 |
# File 'lib/sup/thread.rb', line 403 def el = @messages[.id] return if el. # we've seen it before #puts "adding: #{message.id}, refs #{message.refs.inspect}" el. = ## 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
361 362 363 364 |
# File 'lib/sup/thread.rb', line 361 def add_thread t raise "duplicate" if @threads.values.member? t t.each { |m, *| m } end |
#contains?(m) ⇒ Boolean
277 |
# File 'lib/sup/thread.rb', line 277 def contains? m; contains_id? m.id end |
#contains_id?(id) ⇒ Boolean
275 |
# File 'lib/sup/thread.rb', line 275 def contains_id? id; @messages.member?(id) && !@messages[id].empty? end |
#delete_message(message) ⇒ Object
396 397 398 399 400 |
# File 'lib/sup/thread.rb', line 396 def el = @messages[.id] return unless el. el. = nil end |
#dump(f = $stdout) ⇒ Object
282 283 284 285 286 287 288 289 |
# File 'lib/sup/thread.rb', line 282 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
392 393 394 |
# File 'lib/sup/thread.rb', line 392 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.
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/sup/thread.rb', line 368 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
339 340 341 342 343 344 345 346 347 348 |
# File 'lib/sup/thread.rb', line 339 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
352 353 354 355 356 357 358 |
# File 'lib/sup/thread.rb', line 352 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
324 325 326 327 328 329 |
# File 'lib/sup/thread.rb', line 324 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
331 332 333 334 335 336 |
# File 'lib/sup/thread.rb', line 331 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
280 |
# File 'lib/sup/thread.rb', line 280 def size; @threads.size end |
#thread_for(m) ⇒ Object
276 |
# File 'lib/sup/thread.rb', line 276 def thread_for m; thread_for_id m.id end |
#thread_for_id(mid) ⇒ Object
274 |
# File 'lib/sup/thread.rb', line 274 def thread_for_id mid; @messages.member?(mid) && @messages[mid].root.thread end |
#threads ⇒ Object
279 |
# File 'lib/sup/thread.rb', line 279 def threads; @threads.values end |