File Coverage

blib/lib/IRC/Server/Tree/Network.pm
Criterion Covered Total %
statement 92 98 93.8
branch 39 64 60.9
condition 12 31 38.7
subroutine 16 16 100.0
pod 10 10 100.0
total 169 219 77.1


line stmt bran cond sub pod time code
1             package IRC::Server::Tree::Network;
2              
3             ## An IRC Network with route memoization and simple sanity checks
4             ## IRC::Server::Tree lives in ->tree()
5              
6 1     1   4291 use strictures 1;
  1         8  
  1         24  
7              
8 1     1   65 use Carp;
  1         1  
  1         69  
9 1     1   4 use Scalar::Util 'blessed';
  1         1  
  1         38  
10              
11 1     1   4 use IRC::Server::Tree;
  1         2  
  1         1099  
12              
13             sub new {
14 6     6 1 1022 my $class = shift;
15              
16 6         10 my $memoize = 1;
17             my $tree = sub {
18              
19 6 100   6   22 if (@_ == 1) {
    100          
20 2         3 my $item = $_[0];
21              
22 2 50 33     37 return $item
23             if blessed($item) and $item->isa('IRC::Server::Tree');
24              
25 0 0       0 return IRC::Server::Tree->new($item)
26             if ref $item eq 'ARRAY';
27             } elsif (@_ > 1) {
28             ## Given named opts, we hope.
29             ## memoize => Bool
30             ## tree => IRC::Server::Tree
31 2         5 my %opts = @_;
32 2         13 $opts{lc $_} = delete $opts{$_} for keys %opts;
33              
34 2   50     11 $memoize = $opts{memoize} || 1;
35              
36 2 50       13 return IRC::Server::Tree->new(
37             $opts{tree} ? delete $opts{tree} : ()
38             )
39             }
40              
41 2         7 return IRC::Server::Tree->new
42 6         28 };
43              
44 6         16 my $self = {
45             tree => $tree->(@_),
46             memoize => $memoize,
47             };
48              
49 6         15 bless $self, $class;
50              
51             ## Set up ->{seen}
52 6         13 $self->reset_tree;
53              
54 5         37 $self
55             }
56              
57             sub reset_tree {
58 8     8 1 12 my ($self) = @_;
59              
60             ## Call me for a route clear / seen-item refresh
61             ## (ie, after mucking around in the ->tree() )
62              
63 8         18 $self->{seen} = {};
64              
65 8         22 my $all_names = $self->tree->names_beneath( $self->tree );
66              
67 8         16 for my $name (@$all_names) {
68 24 100       59 if (++$self->{seen}->{$name} > 1) {
69 2         291 confess "Passed a broken Tree; duplicate node entries for $name"
70             }
71             }
72              
73             1
74 6         9 }
75              
76             sub have_peer {
77 11     11 1 19 my ($self, $peer) = @_;
78              
79 11 100       35 return 1 if $self->{seen}->{$peer};
80              
81             return
82 10         26 }
83              
84             sub _have_route_for_peer {
85 9     9   15 my ($self, $peer) = @_;
86              
87 9 50       18 return unless $self->{memoize};
88              
89 9 100       28 if (ref $self->{seen}->{$peer} eq 'ARRAY') {
90 3         11 return $self->{seen}->{$peer}
91             }
92              
93             return
94 6         15 }
95              
96             sub add_peer_to_self {
97 3     3 1 10 my ($self, $peer, $arrayref) = @_;
98              
99 3 50       9 confess "add_peer_to_self expects a peer name"
100             unless defined $peer;
101              
102 3 50       8 if ($arrayref) {
103 0 0 0     0 confess(
      0        
104             "third arg to add_peer_to_name should be an ARRAY or ",
105             "an IRC::Server::Tree"
106             ) unless ref $arrayref eq 'ARRAY'
107             or blessed $arrayref and $arrayref->isa('IRC::Server::Tree');
108             }
109              
110 3 50       8 if ( $self->have_peer($peer) ) {
111 0         0 carp "Tried to add previously-seen node $peer";
112             return
113 0         0 }
114              
115             return unless
116 3 50       7 $self->tree->add_node_to_top($peer, $arrayref);
117 3         9 $self->{seen}->{$peer} = 1;
118             ## reset_tree() if we might've added children
119 3 50       7 $self->reset_tree if $arrayref;
120 3         7 1
121             }
122              
123             sub add_peer_to_name {
124 6     6 1 16 my ($self, $parent_name, $new_name, $arrayref) = @_;
125              
126             ## FIXME
127             ## Hmm.. currently no convenient way to use memoized routes
128             ## when adding.
129             ## Probably should have an add in Tree that can take numerical
130             ## routes to the parent's ref.
131              
132 6 50 33     31 confess "add_peer_to_name expects a parent name and new node name"
133             unless defined $parent_name and defined $new_name;
134              
135 6 100       12 if ($arrayref) {
136 2 50 33     19 confess(
      66        
137             "third arg to add_peer_to_name should be an ARRAY or ",
138             "an IRC::Server::Tree"
139             ) unless ref $arrayref eq 'ARRAY'
140             or blessed $arrayref and $arrayref->isa('IRC::Server::Tree');
141             }
142              
143 6 50       15 if ( $self->have_peer($new_name) ) {
144 0         0 carp "Tried to add previously-seen node $new_name";
145             return
146 0         0 }
147              
148             return unless
149 6 50       13 $self->tree->add_node_to_name($parent_name, $new_name, $arrayref);
150 6         16 $self->{seen}->{$new_name} = 1;
151 6 100       14 $self->reset_tree if $arrayref;
152 5         17 1
153             }
154              
155             sub hop_count {
156             ## Returns a hop count as normally used in LINKS output and similar
157 2     2 1 4 my ($self, $peer) = @_;
158              
159 2 50       7 confess "hop_count expects a peer name"
160             unless defined $peer;
161              
162 2         5 my $path = $self->trace( $peer );
163 2 50       6 return unless $path;
164              
165 2         15 scalar(@$path)
166             }
167              
168             sub split_peer {
169             ## Split a peer and return the names of all hops under it.
170 1     1 1 636 my ($self, $peer) = @_;
171              
172 1 50       5 confess "split_peer expects a peer name"
173             unless defined $peer;
174              
175 1   50     3 my $splitref = $self->tree->del_node_by_name( $peer ) || return;
176              
177 1         3 delete $self->{seen}->{$peer};
178              
179 1         4 my $names = $self->tree->names_beneath( $splitref );
180              
181 1 50 33     7 if ($names && @$names) {
182 1         8 delete $self->{seen}->{$_} for @$names;
183             }
184              
185             $names
186 1         5 }
187              
188             sub split_peer_nodes {
189 1     1 1 9 my ($self, $peer) = @_;
190              
191 1 50       4 confess "split_peer_nodes expects a peer name"
192             unless defined $peer;
193              
194 1   50     3 my $splitref = $self->tree->del_node_by_name($peer) || return;
195 1         3 delete $self->{seen}->{$peer};
196              
197 1 50       2 for my $name (@{ $self->tree->names_beneath($splitref) || [] }) {
  1         5  
198 3         6 delete $self->{seen}->{$name}
199             }
200              
201             $splitref
202 1         6 }
203              
204             sub trace {
205 9     9 1 419 my ($self, $peer) = @_;
206              
207 9 50       19 confess "trace expects a peer name"
208             unless defined $peer;
209              
210 9 100       20 if (my $routed = $self->_have_route_for_peer($peer) ) {
211 3         7 return $self->tree->path_by_indexes( $routed )
212             }
213              
214             ## FIXME maybe needs a switch via the memoize new() opt.
215             ## If we memoize the indexes, we have to walk that path twice.
216             ## (a search to get indexes, a walk to get names)
217             ## If we memoize the route, we spend more memory on hop names.
218 6         11 my $index_route = $self->tree->trace_indexes( $peer );
219 6 100 100     37 return unless ref $index_route eq 'ARRAY' and scalar @$index_route;
220              
221 5         11 my $named_hops = $self->tree->path_by_indexes( $index_route );
222 5 50 50     26 return unless ref $named_hops eq 'ARRAY' and scalar @$named_hops;
223              
224 5 50       18 $self->{seen}->{$peer} = $index_route if $self->{memoize};
225              
226 5         18 $named_hops
227             }
228              
229             sub tree {
230 45     45 1 533 my ($self) = @_;
231 45         163 $self->{tree}
232             }
233              
234             1;
235              
236             =pod
237              
238             =head1 NAME
239              
240             IRC::Server::Tree::Network - An enhanced IRC::Server::Tree
241              
242             =head1 SYNOPSIS
243              
244             ## Model a network
245             my $net = IRC::Server::Tree::Network->new;
246              
247             ## Add a couple top-level peers
248             $net->add_peer_to_self('hubA');
249             $net->add_peer_to_self('leafA');
250              
251             ## Add some peers to hubA
252             $net->add_peer_to_name('hubA', 'leafB');
253             $net->add_peer_to_name('hubA', 'leafC');
254              
255             ## [ 'leafB', 'leafC' ] :
256             my $split = $net->split_peer('hubA');
257              
258             See below for complete details.
259              
260             =head1 DESCRIPTION
261              
262             An IRC::Server::Tree::Network provides simpler methods for interacting
263             with an L. It also handles L route memoization
264             and uniqueness-checking.
265              
266             =head2 new
267              
268             my $net = IRC::Server::Tree::Network->new;
269              
270             ## With named opts:
271             my $net = IRC::Server::Tree::Network->new(
272             tree => $my_tree,
273              
274             ## Turn off route preservation:
275             memoize => 0,
276             );
277              
278             ## With an existing Tree and no other opts:
279             my $net = IRC::Server::Tree::Network->new(
280             IRC::Server::Tree->new( $previous_tree )
281             );
282              
283             The constructor initializes a new Network.
284              
285             B
286              
287             Setting 'memoize' to a false value at construction time will disable
288             route preservation, saving some memory at the expense of more frequent
289             tree searches.
290              
291             B
292              
293             If an existing Tree is passed in, a list of unique node names in the Tree
294             is compiled and validated.
295              
296             Routes are not stored until a L is called.
297              
298             =head2 add_peer_to_self
299              
300             $net->add_peer_to_self( $peer_name );
301              
302             Adds a node identified by the specified peer name to the top level of our
303             tree; i.e., a directly-linked peer.
304              
305             The identifier must be unique. IRC networks may not have duplicate
306             entries in the tree.
307              
308             You can optionally specify an existing tree of nodes to add under the new
309             node as an ARRAY:
310              
311             $net->add_peer_to_self( $peer_name, $array_ref );
312              
313             ...but it will trigger a tree-walk to reset seen peers.
314              
315             =head2 add_peer_to_name
316              
317             $net->add_peer_to_name( $parent_name, $new_peer_name );
318              
319             Add a node identified by the specified C<$new_peer_name> to the specified
320             C<$parent_name>.
321              
322             Returns empty list and warns if the specified parent is not found.
323              
324             Specifying an existing ARRAY of nodes works the same as
325             L.
326              
327             =head2 have_peer
328              
329             if ( $net->have_peer( $peer_name ) ) {
330             . . .
331             }
332              
333             Returns a boolean value indicating whether or not the specified name is
334             already seen in the tree. (This relies on our tracked entries, rather
335             than finding a path for each call.)
336              
337             =head2 hop_count
338              
339             my $count = $net->hop_count( $peer_name );
340              
341             Returns the number of hops to the destination node; i.e., a
342             directly-linked peer is 1 hop away:
343              
344             hubA
345             leafA - 1 hop
346             hubB - 1 hop
347             leafB - 2 hops
348              
349             Returns empty list if the peer was not found.
350              
351             =head2 reset_tree
352              
353             $net->reset_tree;
354              
355             Clears all currently-known routes and re-validates the tree.
356              
357             You shouldn't normally need to call this yourself unless you are in the
358             process of breaking things severely (such as manipulating the stored
359             L).
360              
361             =head2 split_peer
362              
363             my $split_names = $net->split_peer( $peer_name );
364              
365             Splits a node from the tree.
366              
367             Returns an ARRAY containing the names of every node beneath the one that
368             was split, not including the originally specified peer.
369              
370             Returns empty list if the peer was not found.
371              
372             Returns empty arrayref if the node was split but no nodes were underneath
373             the split node.
374              
375             =head2 split_peer_nodes
376              
377             my $split_peer_nodes = $net->split_peer_nodes( $peer_name );
378              
379             Splits a node from the tree just like L, except returns the
380             array-of-arrays forming the tree underneath the split peer.
381              
382             This can be
383             fed back to Network add_peer methods such as L and
384             L:
385              
386             my $split_nodes = $net->split_peer_nodes( 'hubA' );
387             $net->add_peer_to_self( 'NewHub' );
388             $net->add_peer_to_name( 'NewHub', $split_nodes );
389              
390             =head2 trace
391              
392             my $trace_names = $net->trace( $peer_name );
393              
394             A successful trace returns the same value as L;
395             see the documentation for L for details.
396              
397             Returns empty list if the peer was not found.
398              
399             This proxy method memoizes routes for future lookups. They are cleared
400             when L is called.
401              
402             =head2 tree
403              
404             The C method returns the L object belonging to
405             this Network.
406              
407             my $as_hash = $net->tree->as_hash;
408              
409             See the L documentation for details.
410              
411             Note that calling methods on the Tree object that manipulate the tree
412             (adding and deleting nodes) will break future lookups via Network. Don't
413             do that; if you need to manipulate the Tree directly, fetch it, change
414             it, and create a new Network:
415              
416             my $tree = $net->tree;
417              
418             ## ... call methods on the IRC::Server::Tree ...
419             $tree->del_node_by_name('SomeNode');
420              
421             my $new_net = IRC::Server::Tree::Network->new(
422             $tree
423             );
424              
425             ... or if you must, at least call reset_tree to reset our state and
426             validate the tree:
427              
428             $net->tree->del_node_by_name('SomeNode');
429             $net->reset_tree;
430              
431             =head1 AUTHOR
432              
433             Jon Portnoy
434              
435             =cut