File Coverage

blib/lib/Polycom/Contact/Directory.pm
Criterion Covered Total %
statement 101 139 72.6
branch 25 56 44.6
condition 6 16 37.5
subroutine 14 19 73.6
pod 9 9 100.0
total 155 239 64.8


line stmt bran cond sub pod time code
1             package Polycom::Contact::Directory;
2 3     3   4812 use strict;
  3         7  
  3         111  
3 3     3   16 use warnings;
  3         6  
  3         87  
4            
5 3     3   3148 use Encode;
  3         54230  
  3         309  
6 3     3   2736 use IO::File;
  3         37467  
  3         528  
7 3     3   3540 use List::MoreUtils;
  3         4255  
  3         162  
8            
9 3     3   23 use Polycom::Contact;
  3         6  
  3         26  
10            
11             our $VERSION = 0.05;
12            
13             ######################################
14             # Overloaded Operators
15             ######################################
16             use overload (
17 0     0   0 '==' => sub { $_[0]->equals($_[1]) },
18 0     0   0 '!=' => sub { !$_[0]->equals($_[1]) },
19 3     3   338 );
  3         7  
  3         39  
20            
21             ###################
22             # Constructor
23             ###################
24             sub new
25             {
26 7     7 1 7163 my ($class, $file) = @_;
27            
28 7         17 my @contacts;
29 7 100       30 if ($file)
30             {
31 3         6 my $xml;
32            
33 3 50       28 if ($file =~ /<\/item>/)
    0          
    0          
34             {
35 3         8 $xml = $file;
36             }
37             elsif (ref $file)
38             {
39 0         0 binmode($file, ':utf8');
40 0         0 $xml = do { local $/; <$file> };
  0         0  
  0         0  
41             }
42             elsif (-e $file)
43             {
44 0         0 my $fh = IO::File->new($file, '<');
45 0         0 $fh->binmode(':utf8');
46 0         0 $xml = do { local $/; <$fh> };
  0         0  
  0         0  
47             }
48             else
49             {
50 0         0 die "Cannot open '$file'";
51             }
52            
53 3 100       19 if (!utf8::is_utf8($xml))
54             {
55 2         15 $xml = Encode::decode('utf8', $xml);
56             }
57            
58 3         222 while ($xml =~ m/(.*?)<\/item>/gs)
59             {
60 7         26 my $str = $1;
61 7         46 my ($fn) = $str =~ /(.*?)<\/fn>/s;
62 7         44 my ($ln) = $str =~ /(.*?)<\/ln>/s;
63 7         39 my ($ct) = $str =~ /(.*?)<\/ct>/s;
64 7         27 my ($sd) = $str =~ /(.*?)<\/sd>/s;
65 7         21 my ($lb) = $str =~ /(.*?)<\/lb>/s;
66 7         25 my ($rt) = $str =~ /(.*?)<\/rt>/s;
67 7         33 my ($dc) = $str =~ /(.*?)<\/dc>/s;
68 7         21 my ($ar) = $str =~ /(.*?)<\/ar>/s;
69 7         38 my ($ad) = $str =~ /(.*?)<\/ad>/s;
70 7         21 my ($bw) = $str =~ /(.*?)<\/bw>/s;
71 7         58 my ($bb) = $str =~ /(.*?)<\/bb>/s;
72            
73 7         57 foreach ($fn, $ln, $ct, $sd, $lb, $rt, $dc, $ar, $ad, $bw, $bb)
74             {
75 77 100       149 next if !defined;
76 28         50 s/&/&/g;
77 28         44 s/"/"/g;
78 28         38 s/'/'/g;
79 28         37 s/</
80 28         45 s/>/>/g;
81             }
82            
83 7   50     197 push @contacts,
      50        
      50        
      50        
      50        
84             Polycom::Contact->new(
85             first_name => $fn,
86             last_name => $ln,
87             contact => $ct,
88             speed_index => $sd,
89             label => $lb,
90             ring_type => $rt,
91             divert => $dc || 0,
92             auto_reject => $ar || 0,
93             auto_divert => $ad || 0,
94             buddy_watching => $bw || 0,
95             buddy_block => $bb || 0,
96             in_storage => 1,
97             );
98             }
99             }
100            
101 7         50 return bless { contacts => \@contacts, }, $class;
102             }
103            
104             ###################
105             # Public methods
106             ###################
107            
108             sub insert
109             {
110 3     3 1 47 my ($self, @contacts) = @_;
111            
112 3         9 foreach my $c (@contacts)
113             {
114 9         19 $c->{in_storage} = 1;
115 9         47 push @{ $self->{contacts} },
  9         47  
116 9 50       15 UNIVERSAL::isa($c, 'Polycom::Contact') ? $c : Polycom::Contact->new(%{$c});
117             }
118            
119             }
120            
121             sub all
122             {
123 16     16 1 2499 return grep { $_->in_storage } @{ $_[0]->{contacts} };
  37         279  
  16         58  
124             }
125            
126             sub search
127             {
128 9     9 1 19450 my ($self, $cond) = @_;
129 9 50 33     71 return if !defined $cond || !ref $cond;
130            
131 9         13 my @results;
132 9         26 foreach my $c ($self->all)
133             {
134 15 50   15   133 if (List::MoreUtils::all { defined $cond->{$_} && $c->{$_} eq $cond->{$_} }
  15 100       146  
135 15         100 keys %{$cond}
136             )
137             {
138 3         24 push @results, $c;
139             }
140             }
141            
142 9         38 return @results;
143             }
144            
145             sub count
146             {
147 3     3 1 20 my ($self) = @_;
148 3         7 return scalar(grep { $_->in_storage } @{ $self->{contacts} });
  7         288  
  3         150  
149             }
150            
151             sub to_xml
152             {
153 4     4 1 37 my ($self) = @_;
154            
155 4         12 my $xml = '';
156 4         13 $xml .= "\n\n \n";
157 4         16 foreach my $c ($self->all)
158             {
159 9         37 my %f = %{$c};
  9         76  
160 9         36 foreach my $key (keys %f)
161             {
162 108 100       208 next if !defined $f{$key};
163 81         131 $f{$key} =~ s/&/&/g;
164 81         98 $f{$key} =~ s/"/"/g;
165 81         91 $f{$key} =~ s/'/'/g;
166 81         99 $f{$key} =~ s/
167 81         116 $f{$key} =~ s/>/>/g;
168             }
169            
170 9         24 $xml .= " \n";
171 9 50       48 $xml .= " $f{first_name}\n" if defined $f{first_name};
172 9 50       36 $xml .= " $f{last_name}\n" if defined $f{last_name};
173 9 50       32 $xml .= " $f{contact}\n" if defined $f{contact};
174 9 50       25 $xml .= " $f{speed_index}\n" if defined $f{speed_index};
175 9 50       23 $xml .= " $f{label}\n" if defined $f{label};
176 9 50       39 $xml .= " $f{ring_type}\n" if defined $f{ring_type};
177 9 50       34 $xml .= " $f{divert}\n" if defined $f{divert};
178 9 50       33 $xml .= " $f{auto_reject}\n" if defined $f{auto_reject};
179 9 50       31 $xml .= " $f{auto_divert}\n" if defined $f{auto_divert};
180 9 50       43 $xml .= " $f{buddy_watching}\n" if defined $f{buddy_watching};
181 9 50       46 $xml .= " $f{buddy_block}\n" if defined $f{buddy_block};
182 9         37 $xml .= " \n";
183             }
184 4         14 $xml .= " \n";
185            
186 4         15 return $xml;
187             }
188            
189             sub save
190             {
191 0     0 1   my ($self, $filename) = @_;
192 0 0 0       if (!defined $filename || $filename eq '')
193             {
194 0           $filename = '000000000000-directory.xml';
195             }
196            
197 0           my $fh = IO::File->new($filename, '>');
198 0           $fh->binmode(':utf8');
199            
200 0           print $fh $self->to_xml;
201             }
202            
203             sub is_valid
204             {
205 0     0 1   my ($self) = @_;
206            
207 0           my %contact_num;
208             my %speed_index;
209 0           foreach my $c ($self->all)
210             {
211             # Verify that all of the constituent contacts are valid
212 0 0         if (!$c->is_valid)
213             {
214 0           return;
215             }
216            
217             # Verify that there are no duplicate contact values
218 0 0         if (exists $contact_num{ $c->{contact} })
219             {
220 0           return;
221             }
222 0           $contact_num{ $c->{contact} } = 1;
223            
224             # Verify that there are no duplicate speed dial values
225 0 0         if (exists $speed_index{ $c->{speed_index} })
226             {
227 0           return;
228             }
229 0           $speed_index{ $c->{speed_index} } = 1;
230             }
231            
232 0           return 1;
233             }
234            
235             sub equals
236             {
237 0     0 1   my ($self, $other) = @_;
238            
239             # The are unequal if they contain different numbers of contacts
240 0 0         return if $self->count != $other->count;
241            
242 0           my @myAll = $self->all;
243 0           my @otherAll = $other->all;
244 0           for my $i (0 .. @myAll - 1)
245             {
246 0 0         if ($myAll[$i] != $otherAll[$i])
247             {
248 0           return;
249             }
250             }
251            
252 0           return 1;
253             }
254            
255             'Together. Great things happen.';
256            
257             =head1 NAME
258            
259             Polycom::Contact::Directory - Parser for Polycom VoIP phone local contact directory files.
260            
261             =head1 SYNOPSIS
262            
263             use Polycom::Contact::Directory;
264            
265             # Load an existing contact directory file
266             my $dir = Polycom::Contact::Directory->new('0004f21ac123-directory.xml');
267            
268             # Add a contact
269             $dir->insert(
270             { first_name => 'Jenny',
271             last_name => 'Xu',
272             contact => '2',
273             },
274             );
275            
276             # Find some contacts
277             my @all = $dir->all;
278             my @smiths = $dir->search({ last_name => 'Smith' });
279            
280             # Modify a contact in the directory
281             $smiths[0]->last_name('Johnson');
282            
283             # Remove a contact
284             $smiths[1]->delete;
285            
286             # Save the directory to an XML file suitable for being read by the phone
287             $dir->save('0004f21ac123-directory.xml');
288            
289             =head1 DESCRIPTION
290            
291             Polycom SoundPoint IP, SoundStation IP, and VVX VoIP phones maintain a local contact directory that is stored on their configured boot server. Upon boot-up, each phone looks for a file named I<>-directory.xml on their boot server, and if found, downloads and parses the file to build up its local contact directory. In addition to basic contact information such first/last name and phone number, each contact in the local contact directory can also include information about speed-dialing, distinctive ring tones, presence, and instant messaging.
292            
293             Each I<>-directory.xml contains fairly straightforward XML:
294            
295            
296            
297            
298            
299             Doe
300             John
301             1001
302             1
303             1
304            
305             0
306             0
307             0
308             0
309            
310             ...
311            
312            
313            
314             This module parses Polycom VoIP phone local contact directory files, and can be used to read, modify, or create local contact directory files. It also provides a C method that can be used to perform some basic integrity checks on contact directory files.
315            
316             For more information about administering the Local Contact Directory on Polycom SoundPoint IP phones, see the "I" at L.
317            
318             =head1 CONSTRUCTOR
319            
320             =head2 new ( $filename | $file_handle | $xml_contents )
321            
322             # Create a new empty directory
323             my $dir = Polycom::Contact::Directory->new();
324            
325             # Load a directory from a filename or file handle
326             my $dir2 = Polycom::Contact::Directory->new('directory.xml');
327             my $dir3 = Polycom::Contact::Directory->new($fh);
328            
329             If you have already slurped the contents of a contact directory file into a scalar, you can also pass that scalar to C to parse those XML contents.
330            
331             =head1 METHODS
332            
333             =head2 insert ( @contacts )
334            
335             $dir->insert(
336             { first_name => 'Jenny',
337             last_name => 'Xu',
338             contact => '2',
339             speed_index => 1,
340             ring_type => 5,
341             },
342             { first_name => 'Jacky',
343             last_name => 'Cheng',
344             contact => '3',
345             speed_index => 2,
346             ring_type => 10,
347             },
348             );
349            
350             Adds the specified I<@contacts> contacts, if any, to the directory. I<@contacts> may be an array of hash references containing keys like "first_name", "last_name", and "contact", or it can be an array of C objects.
351            
352             =head2 all
353            
354             my @contacts = $dir->all;
355             foreach my $contact (@contacts)
356             {
357             # ...
358             }
359            
360             Returns an array of all of the C objects in the contact directory.
361            
362             =head2 count
363            
364             my $num_contacts = $dir->count;
365            
366             Returns the number of contacts in the directory.
367            
368             =head2 equals ( $directory2 )
369            
370             if ($dir1->equals($dir2))
371             {
372             print "The contact directories are equal\n";
373             }
374            
375             Returns true if both contact directories are equal (i.e. they contain the same contacts).
376            
377             Because the I<==> and I operators have also been overloaded for both C and C objects, it is equivalent to compare two contact directories using:
378            
379             if ($dir1 == $dir2)
380             {
381             print "The contact directories are equal\n";
382             }
383            
384             =head2 is_valid
385            
386             if (!$dir->is_valid)
387             {
388             print "$dir is invalid.\n";
389             }
390            
391             Returns true if each contact is valid (e.g. has a contact number, name is < 40 bytes long, etc), there are no duplicate contact numbers, and there are no duplicate speed index numbers. Otherwise, it returns false.
392            
393             =head2 save ( $filename )
394            
395             $dir->save('0004f21acabf-directory.xml');
396             # or
397             $dir->save()
398            
399             Writes the contents of the contact directory object to the specified file such that a phone should be able to read those contacts from the file if the file is placed on the phone's boot server.
400            
401             If I<$filename> is not specified, the default filename used is "000000000000-directory.xml", which is the filename the phones look for if a directory file whose name contains their MAC address is not found.
402            
403             =head2 search ( $condition )
404            
405             my @smiths = $dir->search({ last_name => 'Smith' });
406            
407             Returns an array of the contacts that match the specified condition. I<$condition> must be a hash reference whose keys are field names of C fields (e.g. first_name, last_name, contact, ring_type, etc). All of the specified conditions must hold in order for a contact to be present in the array returned.
408            
409             =head2 to_xml
410            
411             my $xml = $directory->to_xml;
412            
413             Returns the XML representation of the contact directory. It is exactly this XML representation that the C method writes to the local contact directory file.
414            
415             =head1 SEE ALSO
416            
417             =over
418            
419             =item C - A contact in the local contact directory. Each C object contains zero or more C objects.
420            
421             =item I - L
422            
423             =back
424            
425             =head1 AUTHOR
426            
427             Zachary Blair, Ezblair@cpan.orgE
428            
429             =head1 COPYRIGHT AND LICENSE
430            
431             Copyright (C) 2010 by Polycom Canada
432            
433             This library is free software; you can redistribute it and/or modify
434             it under the same terms as Perl itself, either Perl version 5.8.8 or,
435             at your option, any later version of Perl 5 you may have available.
436            
437             =cut
438