about summary refs log tree commit
path: root/lib/msgthr/container.rb
blob: 256033b71a4ba245a3766cfd866685925155c661 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# Copyright (C) 2016 all contributors <msgthr-public@80x24.org>
# License: GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>

# An internal container class, this is exposed for Msgthr#thread!
# Msgthr#order! and Msgthr#walk_thread APIs through block parameters.
# They should should not be initialized in your own code.
#
# One container object will exist for every message you call Msgthr#add! on,
# so there can potentially be many of these objects for large sets of
# messages.
class Msgthr::Container

  # Unique message identifier, typically the Message-Id header for mail
  # and news messages.  This may be any hashable object, Integer values
  # are allowed and will not be coerced to string values.
  attr_reader :mid

  # Opaque data pointer, may be used by the user for any purpose.
  # This is +nil+ to denote missing (aka "ghost") messages.
  attr_accessor :msg

  # You probably do not need to use this.
  # It is only safe to access this after Msgthr#order!
  # This contains an Array of Msgthr::Container objects which have the
  # +parent+ field pointing to us
  attr_accessor :children

  # You probably do not need to use this; and you should only use
  # this after Msgthr#order!  This points to the +parent+ of the
  # message if one exists, and +nil+ if a message has no parent.
  # This will only be accurate once all messages are added to
  # a Msgthr set via Msgthr#add
  attr_accessor :parent # :nodoc:

  def initialize(mid) # :nodoc:
    @mid = mid
    @children = {} # becomes an Array after order!
    @parent = nil
    @msg = nil # opaque pointer supplied by user
  end

  # Returns the topmost message container with an opaque message pointer
  # in it.  This may be +nil+ if no message is available.
  # This is preferable to using the container yielded by Msgthr#order!
  # directly when handling incomplete message sets.
  def topmost
    q = [ self ]
    while cont = q.shift
      return cont if cont.msg
      q.concat(cont.children.values)
    end
    nil
  end

  def add_child(child) # :nodoc:
    raise 'cannot become child of self' if child == self
    cid = child.mid

    # reparenting:
    parent = child.parent and parent.children.delete(cid)

    @children[cid] = child
    child.parent = self
  end

  def has_descendent(child) # :nodoc:
    seen = {}
    while child
      return true if self == child || seen[child]
      seen[child] = true
      child = child.parent
    end
    false
  end

  # only called by Msgthr#order!
  def order! # :nodoc:
    seen = { @mid => true }
    q = [ self ]
    while cur = q.shift
      c = []
      cur.children.each do |cmid, cont|
        next if seen[cmid]
        c << cont
        seen[cmid] = true
      end.clear
      yield(c) if c.size > 1
      cur.children = c
      q.concat(c)
    end
  end
end