File Coverage

blib/lib/Nokia/File/NFB.pm
Criterion Covered Total %
statement 119 120 99.1
branch 14 20 70.0
condition 2 6 33.3
subroutine 21 21 100.0
pod 8 8 100.0
total 164 175 93.7


line stmt bran cond sub pod time code
1             package Nokia::File::NFB;
2              
3             ## Create, Parse and Write Nokia NFB files.
4             ## Robert Price - http://www.robertprice.co.uk/
5              
6 3     3   72025 use 5.00503;
  3         12  
  3         125  
7 3     3   20 use strict;
  3         5  
  3         107  
8 3     3   3202 use utf8;
  3         35  
  3         17  
9 3     3   123 use vars qw($VERSION);
  3         6  
  3         158  
10              
11 3     3   18 use Carp;
  3         5  
  3         289  
12 3     3   6010 use Compress::Zlib qw(crc32);
  3         283800  
  3         321  
13 3     3   3484 use Encode qw(decode encode);
  3         40197  
  3         323  
14 3     3   27 use Fcntl qw(:seek);
  3         7  
  3         418  
15 3     3   2638 use FileHandle;
  3         3006  
  3         18  
16              
17 3     3   3419 use Nokia::File::NFB::Element;
  3         6  
  3         3804  
18              
19             $VERSION = '0.01';
20              
21              
22             ## new()
23             ## the object constructor.
24             ## OUTPUT: a blessed hash corresponding to the object.
25             sub new {
26 4     4 1 20 my $class = shift;
27 4         9 my $self = {};
28 4         34 bless $self, $class;
29 4         12 return $self;
30             }
31              
32              
33             ## DESTROY()
34             ## destructor for the object. We just make sure we have any
35             ## open file handles closed before we die.
36             sub DESTROY {
37 4     4   1109 my $self = shift;
38              
39             ## close any filehandles we have in the object that are still open.
40 4 0 33     379 $self->{'_fh'}->close() if ((exists $self->{'_fh'}) && ($self->{'_fh'}) && (ref($self->{'_fh'}) eq 'FileHandle'));
      33        
41             }
42              
43              
44             ## version()
45             ## get or set the NFB file version.
46             ## INPUT: a string with the NFB file version in (optional).
47             ## OUTPUT: a string with the NFB file version in.
48             sub version {
49 8     8 1 13 my $self = shift;
50 8 100       27 $self->{'_version'} = $_[0] if ($_[0]);
51 8         25 return $self->{'_version'};
52             }
53              
54              
55             ## firmware()
56             ## get or set the phone firmware relating to the file.
57             ## INPUT: a string with the phone firmware in (optional).
58             ## OUTPUT: a string with the phone firmware in.
59             sub firmware {
60 8     8 1 13 my $self = shift;
61 8 100       24 $self->{'_firmware'} = $_[0] if ($_[0]);
62 8         28 return $self->{'_firmware'};
63             }
64              
65              
66             ## phone()
67             ## get or set the phone model relating to the file.
68             ## INPUT: a string with the phone model in (optional).
69             ## OUTPUT: a string with the phone model in.
70             sub phone {
71 7     7 1 718 my $self = shift;
72 7 100       31 $self->{'_phone'} = $_[0] if ($_[0]);
73 7         26 return $self->{'_phone'};
74             }
75              
76              
77             ## elements()
78             ## return a reference to an array with the elements in.
79             ## INPUT: a reference to an array with elements in (optional).
80             ## OUTPUT: a reference to an array with the elements in.
81             sub elements {
82 4     4 1 6 my $self = shift;
83 4 100       12 if ($_[0]) {
84 2         3 my $elements = $_[0];
85 2 50       8 croak("elements need to be a reference to an ARRAY")
86             unless (ref($elements) eq 'ARRAY');
87 2         6 $self->{'_elements'} = $elements;
88             }
89 4         15 return $self->{'_elements'};
90             }
91              
92              
93             ## read()
94             ## reads in the NFB file from a specified filename, parse it
95             ## and setup the object with the corresponding data.
96             ## INPUT: filename to read.
97             sub read {
98 2     2 1 934 my $self = shift;
99 2         5 my $filename = shift;
100            
101             ## keep a copy of the filename in the object for backup.
102 2         37 $self->{'_filename'} = $filename;
103            
104             ## open the file as readonly, else warn the user.
105 2         19 my $fh = new FileHandle($filename, 'r');
106 2 50       242 croak "Unable to open $filename for reading" unless (defined($fh));
107 2         6 $self->{'_fh'} = $fh;
108            
109             ## ensure we are in binary mode as some system (win32 for example)
110             ## will assume we are handling text otherwise.
111 2         12 binmode $fh, ':raw';
112              
113             ## work out the length of the file and save it in the object.
114 2         13 $fh->seek(-4, SEEK_END);
115 2         24 $self->{'_length'} = $fh->tell();
116              
117             ## work out the checksum for the data and save it in the object.
118 2         17 $fh->seek(0, SEEK_SET);
119 2         23 $self->{'_crc'} = crc32($self->_read($self->{'_length'}));
120            
121             ## work out the file version, this is a little endian long
122 2         13 $fh->seek(0, SEEK_SET);
123 2         28 $self->{'_version'} = unpack('V',$self->_read(4));
124              
125             ## get the id and phone and store in the object.
126 2         10 $self->{'_firmware'} = $self->_readstring();
127 2         6953 $self->{'_phone'} = $self->_readstring();
128            
129             ## parse the main data.
130              
131             ## work out how many elements of data we have stored in the file.
132 2         58 $self->{'_number_of_elements'} = unpack('V', $self->_read(4));
133              
134             ## where we are going to store our elements
135 2         7 $self->{'_elements'} = undef;
136            
137             ## iterate over all the elements in the file.
138 2         11 for (my $i=0; $i<$self->{'_number_of_elements'}; $i++) {
139            
140             ## work out filetype of the element, 1 = file, 2 = directory.
141 4         11 my $filetype = unpack('V', $self->_read(4));
142            
143             ## if we have a file...
144 4 100       19 if ($filetype == 1) {
    50          
145              
146             ## get the filename.
147 2         7 my $path = $self->_readstring();
148            
149             ## get the length of data.
150 2         42 my $length = unpack('V', $self->_read(4));
151              
152             ## get the data itself.
153 2         7 my $fdata = $self->_read($length);
154              
155             ## get the file's timestamp.
156 2         7 my $timestamp = unpack('V', $self->_read(4));
157              
158             ## store it all in an Element, and save.
159 2         16 my $data = Nokia::File::NFB::Element->new({
160             'type' => $filetype,
161             'name' => $path,
162             'size' => $length,
163             'time' => $timestamp,
164             'data' => $fdata,
165             });
166 2         6 push @{$self->{'_elements'}}, $data;
  2         11  
167            
168             ## if we have a directory
169             } elsif ($filetype == 2) {
170              
171             ## get the directory's name.
172 2         7 my $path = $self->_readstring();
173              
174             ## store it all in an Element and save.
175 2         58 my $data = Nokia::File::NFB::Element->new({
176             'type' => $filetype,
177             'name' => $path,
178             });
179 2         7 push @{$self->{'_elements'}}, $data;
  2         11  
180            
181             ## else it's an unknown file type
182             } else {
183 0         0 croak("Unknown element type found in data");
184             }
185             }
186            
187             ## get the checksum from the file.
188 2         7 $self->{'_checksum'} = unpack('V', $self->_read(4));
189            
190             ## close the file and remove references to the filehandle from the object.
191 2         11 $fh->close();
192 2         79 delete $self->{'_fh'};
193             }
194              
195              
196              
197             ## write()
198             ## write out the NFB file as a binary file to a specified filename.
199             ## IN: filename to use
200             sub write {
201 1     1 1 4 my $self = shift;
202 1         2 my $filename = shift;
203              
204             ## open the file for writing and save the handle to the object
205 1         10 my $fh = new FileHandle($filename, 'w');
206 1 50       247 croak "Unable to open $filename for writing" unless (defined($fh));
207 1         2 $self->{'_fh'} = $fh;
208            
209             ## ensure we are in binary mode as some system (win32 for example)
210             ## will assume we are handling text otherwise.
211 1         6 binmode $fh, ':raw';
212              
213             ## write the binary data out
214 1         6 print $fh $self->binary();
215              
216             ## close the file and remove references to the filehandle from the object.
217 1         5 $fh->close();
218 1         69 delete $self->{'_fh'};
219             }
220              
221              
222              
223             ## binary()
224             ## return the NFB file as a binary scalar.
225             ## OUTPUT: The NFB file as a binary scalar variable.
226             sub binary {
227 1     1 1 1 my $self = shift;
228 1         2 my $binfile;
229            
230             ## write out the nfb/c version.
231 1         6 $binfile .= pack('V',$self->{'_version'}); ## pack as little endian long.
232            
233             ## write out the firmware.
234 1         2 $binfile .= pack('V', length($self->{'_firmware'})); ## size of the string.
235 1         6 $binfile .= encode('UCS-2LE', $self->{'_firmware'}); ## and the string itself, in UCS2 (little endian) format.
236              
237             ## write out the phone model.
238 1         4299 $binfile .= pack('V', length($self->{'_phone'}));
239 1         5 $binfile .= encode('UCS-2LE', $self->{'_phone'});
240            
241             ## the number of elements.
242 1         22 $binfile .= pack('V',scalar(@{$self->{'_elements'}}));
  1         5  
243            
244             ## add each element.
245 1         2 foreach my $element (@{$self->{'_elements'}}) {
  1         3  
246 2         10 $binfile .= $element->binary();
247             }
248            
249             ## work out the checksum and add it at the end.
250 1         15 my $checksum = crc32($binfile);
251 1         3 $binfile .= pack('V',$checksum);
252              
253 1         6 return $binfile;
254             }
255              
256              
257             ## _readstring
258             ## utility function to read a UCS2 string and return it in utf8.
259             ## OUTPUT: string - the string in utf8 format.
260             sub _readstring {
261 8     8   13 my $self = shift;
262            
263             ## get the length of the string, as UCS2 is two bytes long, have to double it.
264 8         19 my $length = unpack("V", $self->_read(4)) * 2;
265            
266             ## read, decode and return the string.
267 8         20 my $ucs2string = $self->_read($length);
268 8         30 return decode('UCS-2LE', $ucs2string);
269             }
270              
271              
272              
273             ## _read
274             ## utility function to read data from a FileHandle and return it.
275             ## INPUT: size - the ammount of data to read.
276             ## OUTPUT: data - the data.
277             sub _read {
278 34     34   45 my $self = shift;
279 34         39 my $size = shift;
280 34         34 my $data;
281 34         44 my $fh = $self->{'_fh'};
282 34         104 $fh->read($data, $size);
283 34         319 return $data;
284             }
285              
286             1;
287             __END__