File Coverage

blib/lib/FTN/JAM.pm
Criterion Covered Total %
statement 136 620 21.9
branch 41 332 12.3
condition 6 33 18.1
subroutine 14 23 60.8
pod 18 18 100.0
total 215 1026 20.9


line stmt bran cond sub pod time code
1             # FTN::JAM
2              
3             package FTN::JAM;
4              
5 3     3   60436 use warnings;
  3         8  
  3         93  
6 3     3   14 use strict;
  3         6  
  3         91  
7              
8 3     3   18 use Carp;
  3         15  
  3         221  
9 3     3   2785 use Time::Local;
  3         5616  
  3         196  
10 3     3   2599 use Time::Zone;
  3         5881  
  3         17727  
11              
12             =head1 NAME
13              
14             FTN::JAM - A Perl extension for handleing JAM messagebases.
15              
16             =head1 VERSION
17              
18             Version 0.30
19              
20             =cut
21              
22             our $VERSION = '0.30';
23              
24             =head1 SYNOPSIS
25              
26             This module can be used for operations related to JAM messagebases, including the following:
27             creating or removing a messagebase, listing the contents of a messagebase, adding a
28             message to a messagebase, reading a message in a messagebase, changing a message in a
29             messagebase, or finding a user in a messagebase.
30              
31             Here is an example of how it can being used:
32              
33             use FTN::JAM;
34              
35             my $mb = $ARGV[0];
36             my $basemsgnum = $ARGV[1];
37              
38             my $handle = FTN::JAM::CreateMB($mb,$basemsgnum);
39              
40             FTN::JAM::CloseMB($handle);
41             ...
42              
43              
44             =head1 EXPORT
45              
46             The following functions are available in the module: OpenMB, CreateMB, CloseMB,
47             RemoveMB, LockMB, UnlockMB, ReadMBHeader, WriteMBHeader, GetMBSize, ReadMessage,
48             ChangeMessage, AddMessage, Crc32, FindUser, GetLastRead, SetLastRead, TimeToLocal,
49             and LocalToTime.
50              
51             The global variable $Errnum is used for returning error numbers from functions
52             and can be accessed as $FTN::JAM::Errnum. It defaults to undefined.
53              
54             =cut
55              
56             our $Errnum;
57              
58             =head1 FUNCTIONS
59              
60             =head2 OpenMB
61              
62             Syntax: $handle = FTN::JAM::OpenMB($jampath)
63              
64             =cut
65              
66             sub OpenMB {
67              
68 0 0   0 1 0 my ($jampath) = @_ or croak 'OpenMB requires a base file name and path as a parameter.';
69              
70 0         0 my ($JHR, $JDX, $JDT, $JLR);
71              
72 0         0 my $jhrres = open( $JHR, q{+<}, $jampath . ".jhr" );
73 0         0 my $jdxres = open( $JDX, q{+<}, $jampath . ".jdx" );
74 0         0 my $jdtres = open( $JDT, q{+<}, $jampath . ".jdt" );
75 0         0 my $jlrres = open( $JLR, q{+<}, $jampath . ".jlr" );
76              
77 0 0 0     0 if ( !$jhrres || !$jdxres || !$jdtres || !$jlrres ) {
      0        
      0        
78 0 0       0 if ($jhrres) {
79 0         0 close($JHR);
80             }
81 0 0       0 if ($jdxres) {
82 0         0 close($JDX);
83             }
84 0 0       0 if ($jdtres) {
85 0         0 close($JDT);
86             }
87 0 0       0 if ($jlrres) {
88 0         0 close($JLR);
89             }
90              
91 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
92 0         0 return;
93             }
94              
95 0         0 binmode($JHR);
96 0         0 binmode($JDX);
97 0         0 binmode($JDT);
98 0         0 binmode($JLR);
99              
100 0         0 my $old;
101              
102 0         0 $old = select($JHR);
103 0         0 local $| = 1;
104 0         0 select($old);
105 0         0 $old = select($JDX);
106 0         0 local $| = 1;
107 0         0 select($old);
108 0         0 $old = select($JDT);
109 0         0 local $| = 1;
110 0         0 select($old);
111 0         0 $old = select($JLR);
112 0         0 local $| = 1;
113 0         0 select($old);
114              
115 0         0 my %filehash;
116              
117 0         0 $filehash{jhr} = *$JHR;
118 0         0 $filehash{jdx} = *$JDX;
119 0         0 $filehash{jdt} = *$JDT;
120 0         0 $filehash{jlr} = *$JLR;
121              
122 0         0 return \%filehash;
123             }
124              
125             =head2 CreateMB
126              
127             Syntax: $handle = FTN::JAM::CreateMB($jampath,$basemsg)
128              
129             =cut
130              
131             sub CreateMB {
132 1 50   1 1 244 if ( $#_ != 1 ) {
133 0         0 croak "Wrong number of arguments for FTN::JAM::CreateMB";
134             }
135              
136 1         2 my $jampath = $_[0];
137 1         3 my $basemsg = $_[1];
138              
139 1         26 my $hasjdx = ( -e $jampath . ".jdx" );
140 1         13 my $hasjhr = ( -e $jampath . ".jhr" );
141 1         11 my $hasjdt = ( -e $jampath . ".jdt" );
142 1         11 my $hasjlr = ( -e $jampath . ".jlr" );
143              
144 1 50 33     19 if ( $hasjdx or $hasjhr or $hasjdt or $hasjlr ) {
      33        
      33        
145 0         0 $Errnum = $FTN::JAM::Errnum::BASE_EXISTS;
146 0         0 return;
147             }
148              
149 1         2 my ($JHR, $JDX, $JDT, $JLR);
150              
151 1         61905 my $jhrres = open( $JHR, q{+>}, $jampath . ".jhr" );
152 1         178 my $jdxres = open( $JDX, q{+>}, $jampath . ".jdx" );
153 1         368 my $jdtres = open( $JDT, q{+>}, $jampath . ".jdt" );
154 1         70 my $jlrres = open( $JLR, q{+>}, $jampath . ".jlr" );
155              
156 1 50 33     21 if ( !$jhrres || !$jdxres || !$jdtres || !$jlrres ) {
      33        
      33        
157 0 0       0 if ($jhrres) {
158 0         0 close($JHR);
159             }
160 0 0       0 if ($jdxres) {
161 0         0 close($JDX);
162             }
163 0 0       0 if ($jdtres) {
164 0         0 close($JDT);
165             }
166 0 0       0 if ($jlrres) {
167 0         0 close($JLR);
168             }
169              
170 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
171 0         0 return;
172             }
173              
174 1         5 binmode($JHR);
175 1         15 binmode($JDX);
176 1         2 binmode($JDT);
177 1         2 binmode($JLR);
178              
179 1         2 my $old;
180              
181 1         9 $old = select($JHR);
182 1         9 local $| = 1;
183 1         6 select($old);
184 1         3 $old = select($JDX);
185 1         3 local $| = 1;
186 1         2 select($old);
187 1         3 $old = select($JDT);
188 1         4 local $| = 1;
189 1         3 select($old);
190 1         3 $old = select($JLR);
191 1         4 local $| = 1;
192 1         2 select($old);
193              
194 1         2 my %filehash;
195              
196 1         4 $filehash{jhr} = *$JHR;
197 1         5 $filehash{jdx} = *$JDX;
198 1         4 $filehash{jdt} = *$JDT;
199 1         4 $filehash{jlr} = *$JLR;
200              
201 1         2 my %header;
202              
203 1         14 $header{DateCreated} = TimeToLocal(time);
204 1         156 $header{PasswordCRC} = 0xffffffff;
205 1         3 $header{BaseMsgNum} = $basemsg;
206              
207 1 50       12 if ( !LockMB( \%filehash, 0 ) ) {
208 0         0 CloseMB( \%filehash );
209 0         0 return;
210             }
211              
212 1 50       6 if ( !WriteMBHeader( \%filehash, \%header ) ) {
213 0         0 CloseMB( \%filehash );
214 0         0 return;
215             }
216              
217 1         5 UnlockMB( \%filehash );
218              
219 1         13 return \%filehash;
220             }
221              
222             =head2 CloseMB
223              
224             Syntax: FTN::JAM::CloseMB($handle)
225              
226             =cut
227              
228             sub CloseMB {
229              
230 1 50   1 1 7 my ($handleref) = @_ or croak 'CloseMB requires a hash reference parameter.';
231              
232 1 50       30 close( $$handleref{jdx} ) or croak "Unable to close: $!";
233 1 50       18 close( $$handleref{jhr} ) or croak "Unable to close: $!";
234 1 50       17 close( $$handleref{jdt} ) or croak "Unable to close: $!";
235 1 50       23 close( $$handleref{jlr} ) or croak "Unable to close: $!";
236              
237 1         7 return 1;
238             }
239              
240             =head2 RemoveMB
241              
242             Syntax: FTN::JAM::RemoveMB($jampath)
243              
244             =cut
245              
246             sub RemoveMB {
247              
248 1 50   1 1 5 my ($jampath) = @_ or croak 'RemoveMB requires a base file name and path as a parameter.';
249              
250 1         39 my $hasjdx = ( -e $jampath . ".jdx" );
251 1         15 my $hasjhr = ( -e $jampath . ".jhr" );
252 1         12 my $hasjdt = ( -e $jampath . ".jdt" );
253 1         14 my $hasjlr = ( -e $jampath . ".jlr" );
254              
255 1 50       4 if ($hasjdx) {
256 1 50       114 if ( !unlink( $jampath . ".jdx" ) ) {
257 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
258 0         0 return;
259             }
260             }
261              
262 1 50       5 if ($hasjhr) {
263 1 50       88 if ( !unlink( $jampath . ".jhr" ) ) {
264 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
265 0         0 return;
266             }
267             }
268              
269 1 50       4 if ($hasjdt) {
270 1 50       153 if ( !unlink( $jampath . ".jdt" ) ) {
271 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
272 0         0 return;
273             }
274             }
275              
276 1 50       4 if ($hasjlr) {
277 1 50       65 if ( !unlink( $jampath . ".jlr" ) ) {
278 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
279 0         0 return;
280             }
281             }
282              
283 1         7 return 1;
284             }
285              
286             =head2 LockMB
287              
288             Syntax: $success = FTN::JAM::LockMB($handle,$timeout)
289              
290             =cut
291              
292             sub LockMB {
293 1 50   1 1 7 if ( $#_ != 1 ) {
294 0         0 croak "Wrong number of arguments for FTN::JAM::LockMB";
295             }
296              
297 1         3 my $handleref = $_[0];
298 1         2 my $timeout = $_[1];
299              
300 1 50       5 if ( $$handleref{locked} ) {
301 0         0 return 1;
302             }
303              
304 1 50       14 if ( flock( $$handleref{jhr}, 6 ) ) {
305 1         2 $$handleref{locked} = 1;
306 1         7 return 1;
307             }
308              
309 0         0 for ( my $i = 0 ; $i < $timeout ; $i++ ) {
310 0         0 sleep(1);
311              
312 0 0       0 if ( flock( $$handleref{jhr}, 6 ) ) {
313 0         0 $$handleref{locked} = 1;
314 0         0 return 1;
315             }
316             }
317              
318 0         0 $Errnum = $FTN::JAM::Errnum::BASE_NOT_LOCKED;
319 0         0 return;
320             }
321              
322             =head2 UnlockMB
323              
324             Syntax: FTN::JAM::UnlockMB($handle)
325              
326             =cut
327              
328             sub UnlockMB {
329              
330 1 50   1 1 5 my ($handleref) = @_ or croak 'UnlockMB requires a reference to a file hash as a parameter.';
331              
332 1 50       3 if ( $$handleref{locked} ) {
333 1         8 flock( $$handleref{jhr}, 8 );
334 1         21 delete $$handleref{locked};
335             }
336 1         3 return 1;
337             }
338              
339             =head2 ReadMBHeader
340              
341             Syntax: $success = FTN::JAM::ReadMBHeader($handle,\%header)
342              
343             =cut
344              
345             sub ReadMBHeader {
346 1 50   1 1 4514 if ( $#_ != 1 ) {
347 0         0 croak "Wrong number of arguments for FTN::JAM::ReadMBHeader";
348             }
349              
350 1         3 my $handleref = $_[0];
351 1         3 my $headerref = $_[1];
352              
353 1         2 my $buf;
354             my @data;
355              
356 1 50       12 if ( !seek( $$handleref{jhr}, 0, 0 ) ) {
357 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
358 0         0 return;
359             }
360              
361 1 50       33 if ( read( $$handleref{jhr}, $buf, 1024 ) != 1024 ) {
362 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
363 0         0 return;
364             }
365              
366 1         26 @data = unpack( "Z[4]LLLLL", $buf );
367              
368 1 50       6 if ( $data[0] ne "JAM" ) {
369 0         0 $Errnum = $FTN::JAM::Errnum::BASEHEADER_CORRUPT;
370 0         0 return;
371             }
372              
373 1         4 %$headerref = ();
374              
375 1         3 $$headerref{Signature} = $data[0];
376 1         4 $$headerref{DateCreated} = $data[1];
377 1         2 $$headerref{ModCounter} = $data[2];
378 1         3 $$headerref{ActiveMsgs} = $data[3];
379 1         4 $$headerref{PasswordCRC} = $data[4];
380 1         2 $$headerref{BaseMsgNum} = $data[5];
381              
382 1         10 return 1;
383             }
384              
385             =head2 WriteMBHeader
386              
387             Syntax: $success = FTN::JAM::WriteMBHeader($handle,\%header)
388              
389             =cut
390              
391             sub WriteMBHeader {
392 1 50   1 1 5 if ( $#_ != 1 ) {
393 0         0 croak "Wrong number of arguments for FTN::JAM::WriteMBHeader";
394             }
395              
396 1         2 my $handleref = $_[0];
397 1         1 my $headerref = $_[1];
398              
399 1 50       5 if ( !defined( $$headerref{DateCreated} ) ) {
400 0         0 $$headerref{DateCreated} = 0;
401             }
402 1 50       4 if ( !defined( $$headerref{ModCounter} ) ) { $$headerref{ModCounter} = 0; }
  1         2  
403 1 50       4 if ( !defined( $$headerref{ActiveMsgs} ) ) { $$headerref{ActiveMsgs} = 0; }
  1         2  
404 1 50       4 if ( !defined( $$headerref{PasswordCRC} ) ) {
405 0         0 $$headerref{PasswordCRC} = 0;
406             }
407 1 50       4 if ( !defined( $$headerref{BaseMsgNum} ) ) { $$headerref{BaseMsgNum} = 0; }
  0         0  
408              
409 1 50       5 if ( !$$handleref{locked} ) {
410 0         0 $Errnum = $FTN::JAM::Errnum::BASE_NOT_LOCKED;
411 0         0 return;
412             }
413              
414 1         8 $$headerref{Signature} = "JAM";
415 1         2 $$headerref{ModCounter}++;
416              
417 1 50       9 if ( !seek( $$handleref{jhr}, 0, 0 ) ) {
418 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
419 0         0 return;
420             }
421              
422 1         2 my $printres = print { $$handleref{jhr} } pack(
  1         86  
423             "Z[4]LLLLLx[1000]",
424             $$headerref{Signature}, $$headerref{DateCreated},
425             $$headerref{ModCounter}, $$headerref{ActiveMsgs},
426             $$headerref{PasswordCRC}, $$headerref{BaseMsgNum}
427             );
428              
429 1 50       4 if ( !$printres ) {
430 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
431 0         0 return;
432             }
433              
434 1         6 return 1;
435             }
436              
437             =head2 GetMBSize
438              
439             Syntax: $success = FTN::JAM::GetMBSize($handle,\$num)
440              
441             =cut
442              
443             sub GetMBSize {
444 1 50   1 1 9 if ( $#_ != 1 ) {
445 0         0 croak "Wrong number of arguments for FTN::JAM::GetMBSize";
446             }
447              
448 1         3 my $handleref = $_[0];
449 1         3 my $numref = $_[1];
450              
451 1         3 my $buf;
452             my @data;
453              
454 1 50       14 if ( !seek( $$handleref{jdx}, 0, 2 ) ) {
455 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
456 0         0 return;
457             }
458              
459 1         4 my $offset = tell( $$handleref{jdx} );
460              
461 1 50       5 if ( $offset == -1 ) {
462 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
463 0         0 return;
464             }
465              
466 1         4 $$numref = $offset / 8;
467              
468 1         8 return 1;
469             }
470              
471             =head2 ReadMessage
472              
473             Syntax: $success = FTN::JAM::ReadMessage($handle,$msgnum,\%header,\@subfields,\$text)
474              
475             =cut
476              
477             sub ReadMessage {
478 0 0   0 1 0 if ( $#_ != 4 ) {
479 0         0 croak "Wrong number of arguments for FTN::JAM::ReadMessage";
480             }
481              
482 0         0 my $handleref = $_[0];
483 0         0 my $msgnum = $_[1];
484 0         0 my $headerref = $_[2];
485 0         0 my $subfieldsref = $_[3];
486 0         0 my $textref = $_[4];
487              
488 0         0 my $buf;
489             my @data;
490 0         0 my %mbheader;
491              
492 0 0       0 if ( !ReadMBHeader( $handleref, \%mbheader ) ) {
493 0         0 return;
494             }
495              
496 0 0       0 if ( !seek( $$handleref{jdx}, ( $msgnum - $mbheader{BaseMsgNum} ) * 8, 0 ) )
497             {
498 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
499 0         0 return;
500             }
501              
502 0 0       0 if ( read( $$handleref{jdx}, $buf, 8 ) != 8 ) {
503 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
504 0         0 return;
505             }
506              
507 0         0 @data = unpack( "LL", $buf );
508              
509 0 0 0     0 if ( $data[0] == 0xffffffff and $data[1] == 0xffffffff ) {
510 0         0 $Errnum = $FTN::JAM::Errnum::MSG_DELETED;
511 0         0 return;
512             }
513              
514 0 0       0 if ( !seek( $$handleref{jhr}, $data[1], 0 ) ) {
515 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
516 0         0 return;
517             }
518              
519 0 0       0 if ( read( $$handleref{jhr}, $buf, 76 ) != 76 ) {
520 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
521 0         0 return;
522             }
523              
524 0         0 @data = unpack( "Z[4]SSLLLLLLLLLLLLLLLLL", $buf );
525              
526 0 0       0 if ( $data[0] ne "JAM" ) {
527 0         0 $Errnum = $FTN::JAM::Errnum::MSGHEADER_CORRUPT;
528 0         0 return;
529             }
530              
531 0 0       0 if ( $data[1] != 1 ) {
532 0         0 $Errnum = $FTN::JAM::Errnum::MSGHEADER_UNKNOWN;
533 0         0 return;
534             }
535              
536 0         0 %$headerref = ();
537              
538 0         0 $$headerref{Signature} = $data[0];
539 0         0 $$headerref{Revision} = $data[1];
540 0         0 $$headerref{ReservedWord} = $data[2];
541 0         0 $$headerref{SubfieldLen} = $data[3];
542 0         0 $$headerref{TimesRead} = $data[4];
543 0         0 $$headerref{MsgIdCRC} = $data[5];
544 0         0 $$headerref{ReplyCRC} = $data[6];
545 0         0 $$headerref{ReplyTo} = $data[7];
546 0         0 $$headerref{Reply1st} = $data[8];
547 0         0 $$headerref{ReplyNext} = $data[9];
548 0         0 $$headerref{DateWritten} = $data[10];
549 0         0 $$headerref{DateReceived} = $data[11];
550 0         0 $$headerref{DateProcessed} = $data[12];
551 0         0 $$headerref{MsgNum} = $data[13];
552 0         0 $$headerref{Attributes} = $data[14];
553 0         0 $$headerref{Attributes2} = $data[15];
554 0         0 $$headerref{TxtOffset} = $data[16];
555 0         0 $$headerref{TxtLen} = $data[17];
556 0         0 $$headerref{PasswordCRC} = $data[18];
557 0         0 $$headerref{Cost} = $data[19];
558              
559 0 0       0 if ($subfieldsref) {
560 0 0       0 if (
561             read( $$handleref{jhr}, $buf, $$headerref{SubfieldLen} ) !=
562             $$headerref{SubfieldLen} )
563             {
564 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
565 0         0 return;
566             }
567              
568 0         0 @$subfieldsref = ();
569              
570 0         0 while ($buf) {
571 0         0 @data = unpack( "LL", $buf );
572 0         0 push( @$subfieldsref, $data[0] );
573 0         0 push( @$subfieldsref, substr( $buf, 8, $data[1] ) );
574 0         0 $buf = substr( $buf, 8 + $data[1] );
575             }
576             }
577              
578 0 0       0 if ($textref) {
579 0 0       0 if ( !seek( $$handleref{jdt}, $$headerref{TxtOffset}, 0 ) ) {
580 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
581 0         0 return;
582             }
583              
584 0 0       0 if (
585             read( $$handleref{jdt}, $$textref, $$headerref{TxtLen} ) !=
586             $$headerref{TxtLen} )
587             {
588 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
589 0         0 return;
590             }
591             }
592              
593 0         0 return 1;
594             }
595              
596             =head2 ChangeMessage
597              
598             Syntax: $success = FTN::JAM::ChangeMessage($handle,$msgnum,\%header)
599              
600             =cut
601              
602             sub ChangeMessage {
603 0 0   0 1 0 if ( $#_ != 2 ) {
604 0         0 croak "Wrong number of arguments for FTN::JAM::ChangeMessage";
605             }
606              
607 0         0 my $handleref = $_[0];
608 0         0 my $msgnum = $_[1];
609 0         0 my $headerref = $_[2];
610              
611 0 0       0 if ( !defined( $$headerref{Signature} ) ) {
612 0         0 $$headerref{Signature} = "JAM";
613             }
614 0 0       0 if ( !defined( $$headerref{Revision} ) ) { $$headerref{Revision} = 1; }
  0         0  
615 0 0       0 if ( !defined( $$headerref{ReservedWord} ) ) {
616 0         0 $$headerref{ReservedWord} = 0;
617             }
618 0 0       0 if ( !defined( $$headerref{SubfieldLen} ) ) {
619 0         0 $$headerref{SubfieldLen} = 0;
620             }
621 0 0       0 if ( !defined( $$headerref{TimesRead} ) ) { $$headerref{TimesRead} = 0; }
  0         0  
622 0 0       0 if ( !defined( $$headerref{MsgIdCRC} ) ) {
623 0         0 $$headerref{MsgIdCRC} = 0xffffffff;
624             }
625 0 0       0 if ( !defined( $$headerref{ReplyCRC} ) ) {
626 0         0 $$headerref{ReplyCRC} = 0xffffffff;
627             }
628 0 0       0 if ( !defined( $$headerref{ReplyTo} ) ) { $$headerref{ReplyTo} = 0; }
  0         0  
629 0 0       0 if ( !defined( $$headerref{Reply1st} ) ) { $$headerref{Reply1st} = 0; }
  0         0  
630 0 0       0 if ( !defined( $$headerref{ReplyNext} ) ) { $$headerref{ReplyNext} = 0; }
  0         0  
631 0 0       0 if ( !defined( $$headerref{DateWritten} ) ) {
632 0         0 $$headerref{DateWritten} = 0;
633             }
634 0 0       0 if ( !defined( $$headerref{DateReceived} ) ) {
635 0         0 $$headerref{DateReceived} = 0;
636             }
637 0 0       0 if ( !defined( $$headerref{DateProcessed} ) ) {
638 0         0 $$headerref{DateProcessed} = 0;
639             }
640 0 0       0 if ( !defined( $$headerref{MsgNum} ) ) { $$headerref{MsgNum} = 0; }
  0         0  
641 0 0       0 if ( !defined( $$headerref{Attributes} ) ) { $$headerref{Attributes} = 0; }
  0         0  
642 0 0       0 if ( !defined( $$headerref{Attributes2} ) ) {
643 0         0 $$headerref{Attributes2} = 0;
644             }
645 0 0       0 if ( !defined( $$headerref{TxtOffset} ) ) { $$headerref{TxtOffset} = 0; }
  0         0  
646 0 0       0 if ( !defined( $$headerref{TxtLen} ) ) { $$headerref{TxtLen} = 0; }
  0         0  
647 0 0       0 if ( !defined( $$headerref{PasswordCRC} ) ) {
648 0         0 $$headerref{PasswordCRC} = 0xffffffff;
649             }
650 0 0       0 if ( !defined( $$headerref{Cost} ) ) { $$headerref{Cost} = 0; }
  0         0  
651              
652 0 0       0 if ( !$$handleref{locked} ) {
653 0         0 $Errnum = $FTN::JAM::Errnum::BASE_NOT_LOCKED;
654 0         0 return;
655             }
656              
657 0         0 my $buf;
658             my @data;
659 0         0 my %mbheader;
660              
661 0 0       0 if ( !ReadMBHeader( $handleref, \%mbheader ) ) {
662 0         0 return;
663             }
664              
665 0 0       0 if ( ( $$headerref{Attributes} & $FTN::JAM::Attr::DELETED ) ) {
666 0         0 my %oldheader;
667              
668 0 0       0 if ( !ReadMessage( $handleref, $msgnum, \%oldheader, 0, 0 ) ) {
669 0         0 return;
670             }
671              
672 0 0       0 if ( !( $oldheader{Attributes} & $FTN::JAM::Attr::DELETED ) ) {
673 0 0       0 if ( $mbheader{ActiveMsgs} ) {
674 0         0 $mbheader{ActiveMsgs}--;
675             }
676             }
677             }
678              
679 0 0       0 if ( !seek( $$handleref{jdx}, ( $msgnum - $mbheader{BaseMsgNum} ) * 8, 0 ) )
680             {
681 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
682 0         0 return;
683             }
684              
685 0 0       0 if ( read( $$handleref{jdx}, $buf, 8 ) != 8 ) {
686 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
687 0         0 return;
688             }
689              
690 0         0 @data = unpack( "LL", $buf );
691              
692 0 0       0 if ( !seek( $$handleref{jhr}, $data[1], 0 ) ) {
693 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
694 0         0 return;
695             }
696              
697 0         0 my $printres = print { $$handleref{jhr} } pack(
  0         0  
698             "Z[4]SSLLLLLLLLLLLLLLLLL",
699             $$headerref{Signature}, $$headerref{Revision},
700             $$headerref{ReservedWord}, $$headerref{SubfieldLen},
701             $$headerref{TimesRead}, $$headerref{MsgIdCRC},
702             $$headerref{ReplyCRC}, $$headerref{ReplyTo},
703             $$headerref{Reply1st}, $$headerref{ReplyNext},
704             $$headerref{DateWritten}, $$headerref{DateReceived},
705             $$headerref{DateProcessed}, $$headerref{MsgNum},
706             $$headerref{Attributes}, $$headerref{Attributes2},
707             $$headerref{TxtOffset}, $$headerref{TxtLen},
708             $$headerref{PasswordCRC}, $$headerref{Cost}
709             );
710              
711 0 0       0 if ( !$printres ) {
712 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
713 0         0 return;
714             }
715              
716 0 0       0 if ( !WriteMBHeader( $handleref, \%mbheader ) ) {
717 0         0 return;
718             }
719              
720 0         0 return 1;
721             }
722              
723             =head2 AddMessage
724              
725             Syntax: $success = FTN::JAM::AddMessage($handle,\%header,\@subfields,\$text)
726              
727             =cut
728              
729             sub AddMessage {
730 0 0   0 1 0 if ( $#_ != 3 ) {
731 0         0 croak "Wrong number of arguments for FTN::JAM::AddMessage";
732             }
733              
734 0         0 my $handleref = $_[0];
735 0         0 my $headerref = $_[1];
736 0         0 my $subfieldsref = $_[2];
737 0         0 my $textref = $_[3];
738              
739 0         0 my %mbheader;
740             my $printres;
741              
742 0 0       0 if ( !$headerref ) {
743 0 0       0 if ( !ReadMBHeader( $handleref, \%mbheader ) ) {
744 0         0 return;
745             }
746              
747 0 0       0 if ( !seek( $$handleref{jdx}, 0, 2 ) ) {
748 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
749 0         0 return;
750             }
751              
752 0         0 my $jdxoffset = tell( $$handleref{jdx} );
753              
754 0 0       0 if ( $jdxoffset == -1 ) {
755 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
756 0         0 return;
757             }
758              
759 0         0 print { $$handleref{jdx} } pack( "LL", 0xffffffff, 0xffffffff );
  0         0  
760              
761 0 0       0 if ( !$printres ) {
762 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
763 0         0 return;
764             }
765              
766 0         0 return $jdxoffset / 8 + $mbheader{BaseMsgNum};
767             }
768              
769 0 0       0 if ( !defined( $$headerref{Signature} ) ) {
770 0         0 $$headerref{Signature} = "JAM";
771             }
772 0 0       0 if ( !defined( $$headerref{Revision} ) ) { $$headerref{Revision} = 1; }
  0         0  
773 0 0       0 if ( !defined( $$headerref{ReservedWord} ) ) {
774 0         0 $$headerref{ReservedWord} = 0;
775             }
776 0 0       0 if ( !defined( $$headerref{SubfieldLen} ) ) {
777 0         0 $$headerref{SubfieldLen} = 0;
778             }
779 0 0       0 if ( !defined( $$headerref{TimesRead} ) ) { $$headerref{TimesRead} = 0; }
  0         0  
780 0 0       0 if ( !defined( $$headerref{MsgIdCRC} ) ) {
781 0         0 $$headerref{MsgIdCRC} = 0xffffffff;
782             }
783 0 0       0 if ( !defined( $$headerref{ReplyCRC} ) ) {
784 0         0 $$headerref{ReplyCRC} = 0xffffffff;
785             }
786 0 0       0 if ( !defined( $$headerref{ReplyTo} ) ) { $$headerref{ReplyTo} = 0; }
  0         0  
787 0 0       0 if ( !defined( $$headerref{Reply1st} ) ) { $$headerref{Reply1st} = 0; }
  0         0  
788 0 0       0 if ( !defined( $$headerref{ReplyNext} ) ) { $$headerref{ReplyNext} = 0; }
  0         0  
789 0 0       0 if ( !defined( $$headerref{DateWritten} ) ) {
790 0         0 $$headerref{DateWritten} = 0;
791             }
792 0 0       0 if ( !defined( $$headerref{DateReceived} ) ) {
793 0         0 $$headerref{DateReceived} = 0;
794             }
795 0 0       0 if ( !defined( $$headerref{DateProcessed} ) ) {
796 0         0 $$headerref{DateProcessed} = 0;
797             }
798 0 0       0 if ( !defined( $$headerref{MsgNum} ) ) { $$headerref{MsgNum} = 0; }
  0         0  
799 0 0       0 if ( !defined( $$headerref{Attributes} ) ) { $$headerref{Attributes} = 0; }
  0         0  
800 0 0       0 if ( !defined( $$headerref{Attributes2} ) ) {
801 0         0 $$headerref{Attributes2} = 0;
802             }
803 0 0       0 if ( !defined( $$headerref{TxtOffset} ) ) { $$headerref{TxtOffset} = 0; }
  0         0  
804 0 0       0 if ( !defined( $$headerref{TxtLen} ) ) { $$headerref{TxtLen} = 0; }
  0         0  
805 0 0       0 if ( !defined( $$headerref{PasswordCRC} ) ) {
806 0         0 $$headerref{PasswordCRC} = 0xffffffff;
807             }
808 0 0       0 if ( !defined( $$headerref{Cost} ) ) { $$headerref{Cost} = 0; }
  0         0  
809              
810 0 0       0 if ( !$$handleref{locked} ) {
811 0         0 $Errnum = $FTN::JAM::Errnum::BASE_NOT_LOCKED;
812 0         0 return;
813             }
814              
815 0         0 my $buf;
816             my @data;
817              
818 0 0       0 if ( !ReadMBHeader( $handleref, \%mbheader ) ) {
819 0         0 return;
820             }
821              
822 0         0 $$headerref{TxtOffset} = 0;
823 0         0 $$headerref{TxtLen} = 0;
824              
825 0 0 0     0 if ( $textref and length($$textref) ) {
826 0 0       0 if ( !seek( $$handleref{jdt}, 0, 2 ) ) {
827 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
828 0         0 return;
829             }
830              
831 0         0 my $jdtoffset = tell( $$handleref{jdt} );
832              
833 0 0       0 if ( $jdtoffset == -1 ) {
834 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
835 0         0 return;
836             }
837              
838 0         0 $$headerref{TxtOffset} = $jdtoffset;
839 0         0 $$headerref{TxtLen} = length($$textref);
840              
841 0         0 $printres = print { $$handleref{jdt} } $$textref;
  0         0  
842              
843 0 0       0 if ( !$printres ) {
844 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
845 0         0 return;
846             }
847             }
848              
849 0         0 $$headerref{SubfieldLen} = 0;
850 0         0 $$headerref{MsgIdCRC} = 0xffffffff;
851 0         0 $$headerref{ReplyCRC} = 0xffffffff;
852 0         0 my $usercrc = 0xffffffff;
853              
854 0         0 for ( my $i = 0 ; $i <= $#$subfieldsref ; $i = $i + 2 ) {
855 0 0       0 if ( $$subfieldsref[$i] == $FTN::JAM::Subfields::RECVRNAME ) {
856 0         0 $usercrc = Crc32( $$subfieldsref[ $i + 1 ] );
857             }
858              
859 0 0       0 if ( $$subfieldsref[$i] == $FTN::JAM::Subfields::MSGID ) {
860 0         0 $$headerref{MsgIdCRC} = Crc32( $$subfieldsref[ $i + 1 ] );
861             }
862              
863 0 0       0 if ( $$subfieldsref[$i] == $FTN::JAM::Subfields::REPLYID ) {
864 0         0 $$headerref{ReplyCRC} = Crc32( $$subfieldsref[ $i + 1 ] );
865             }
866              
867 0         0 $$headerref{SubfieldLen} += 8 + length( $$subfieldsref[ $i + 1 ] );
868             }
869              
870 0 0       0 if ( !seek( $$handleref{jdx}, 0, 2 ) ) {
871 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
872 0         0 return;
873             }
874              
875 0         0 my $jdxoffset = tell( $$handleref{jdx} );
876              
877 0 0       0 if ( $jdxoffset == -1 ) {
878 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
879 0         0 return;
880             }
881              
882 0         0 $$headerref{MsgNum} = $jdxoffset / 8 + $mbheader{BaseMsgNum};
883 0         0 $$headerref{Signature} = "JAM";
884 0         0 $$headerref{Revision} = 1;
885              
886 0 0       0 if ( !seek( $$handleref{jhr}, 0, 2 ) ) {
887 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
888 0         0 return;
889             }
890              
891 0         0 my $jhroffset = tell( $$handleref{jhr} );
892              
893 0 0       0 if ( $jhroffset == -1 ) {
894 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
895 0         0 return;
896             }
897              
898 0         0 $printres = print { $$handleref{jhr} } pack(
  0         0  
899             "Z[4]SSLLLLLLLLLLLLLLLLL",
900             $$headerref{Signature}, $$headerref{Revision},
901             $$headerref{ReservedWord}, $$headerref{SubfieldLen},
902             $$headerref{TimesRead}, $$headerref{MsgIdCRC},
903             $$headerref{ReplyCRC}, $$headerref{ReplyTo},
904             $$headerref{Reply1st}, $$headerref{ReplyNext},
905             $$headerref{DateWritten}, $$headerref{DateReceived},
906             $$headerref{DateProcessed}, $$headerref{MsgNum},
907             $$headerref{Attributes}, $$headerref{Attributes2},
908             $$headerref{TxtOffset}, $$headerref{TxtLen},
909             $$headerref{PasswordCRC}, $$headerref{Cost}
910             );
911              
912 0 0       0 if ( !$printres ) {
913 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
914 0         0 return;
915             }
916              
917 0         0 for ( my $i = 0 ; $i <= $#$subfieldsref ; $i = $i + 2 ) {
918 0         0 $printres =
919 0         0 print { $$handleref{jhr} }
920             pack( "LL", $$subfieldsref[$i], length( $$subfieldsref[ $i + 1 ] ) ),
921             $$subfieldsref[ $i + 1 ];
922              
923 0 0       0 if ( !$printres ) {
924 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
925 0         0 return;
926             }
927             }
928              
929 0         0 $printres = print { $$handleref{jdx} } pack( "LL", $usercrc, $jhroffset );
  0         0  
930              
931 0 0       0 if ( !$printres ) {
932 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
933 0         0 return;
934             }
935              
936 0 0       0 if ( !( $$headerref{Attributes} & $FTN::JAM::Attr::DELETED ) ) {
937 0         0 $mbheader{ActiveMsgs}++;
938             }
939              
940 0 0       0 if ( !FTN::JAM::WriteMBHeader( $handleref, \%mbheader ) ) {
941 0         0 return;
942             }
943              
944 0         0 return $$headerref{MsgNum};
945             }
946              
947             =head2 Crc32
948              
949             Syntax: $crc32 = FTN::JAM::Crc32($data)
950              
951             =cut
952              
953             sub Crc32 {
954              
955 0 0   0 1 0 my ($data) = @_ or croak 'Crc32 requires the data to be checked as a parameter.';
956              
957 0         0 my $crc;
958             my @table;
959 0         0 my $i;
960 0         0 my $j;
961              
962 0         0 for ( $i = 0 ; $i < 256 ; $i++ ) {
963 0         0 $crc = $i;
964              
965 0         0 for ( $j = 8 ; $j > 0 ; $j-- ) {
966 0 0       0 if ( $crc & 1 ) {
967 0         0 $crc = ( $crc >> 1 ) ^ 0xedb88320;
968             }
969             else {
970 0         0 $crc >>= 1;
971             }
972             }
973              
974 0         0 $table[$i] = $crc;
975             }
976              
977 0         0 $crc = 0xffffffff;
978              
979 0         0 for ( $i = 0 ; $i < length($data) ; $i++ ) {
980 0         0 $crc =
981             ( ( $crc >> 8 ) & 0x00ffffff )
982             ^ $table[ ( $crc ^ ord( lc( substr( $data, $i, 1 ) ) ) ) & 0xff ];
983             }
984              
985 0         0 return $crc;
986             }
987              
988             =head2 FindUser
989              
990             Syntax: $msgnum = FTN::JAM::FindUser($handle,$usercrc,$start)
991              
992             =cut
993              
994             sub FindUser {
995 0 0   0 1 0 if ( $#_ != 2 ) {
996 0         0 croak "Wrong number of arguments for FTN::JAM::FindUser";
997             }
998              
999 0         0 my $handleref = $_[0];
1000 0         0 my $usercrc = $_[1];
1001 0         0 my $start = $_[2];
1002              
1003 0         0 my %mbheader;
1004              
1005 0 0       0 if ( !ReadMBHeader( $handleref, \%mbheader ) ) {
1006 0         0 return;
1007             }
1008              
1009 0 0       0 if ( !seek( $$handleref{jdx}, ( $start - $mbheader{BaseMsgNum} ) * 8, 0 ) )
1010             {
1011 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
1012 0         0 return;
1013             }
1014              
1015 0         0 my $msgnum = $start;
1016              
1017 0         0 my $buf;
1018             my @data;
1019              
1020 0         0 while (1) {
1021 0 0       0 if ( read( $$handleref{jdx}, $buf, 8 ) != 8 ) {
1022 0 0       0 if ( eof( $$handleref{jdx} ) ) {
1023 0         0 $Errnum = $FTN::JAM::Errnum::USER_NOT_FOUND;
1024             }
1025             else {
1026 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
1027             }
1028              
1029 0         0 return;
1030             }
1031              
1032 0         0 @data = unpack( "LL", $buf );
1033              
1034 0 0       0 if ( $data[0] == $usercrc ) {
1035 0         0 return $msgnum;
1036             }
1037              
1038 0         0 $msgnum++;
1039             }
1040 0         0 return;
1041             }
1042              
1043             =head2 GetLastRead
1044              
1045             Syntax: $success = FTN::JAM::GetLastRead($handle,$usernum,\%lastread)
1046              
1047             =cut
1048              
1049             sub GetLastRead {
1050 0 0   0 1 0 if ( $#_ != 2 ) {
1051 0         0 croak "Wrong number of arguments for FTN::JAM::GetLastRead";
1052             }
1053              
1054 0         0 my $handleref = $_[0];
1055 0         0 my $usernum = $_[1];
1056 0         0 my $lastreadref = $_[2];
1057              
1058 0 0       0 if ( !seek( $$handleref{jlr}, 0, 0 ) ) {
1059 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
1060 0         0 return;
1061             }
1062              
1063 0         0 my $buf;
1064             my @data;
1065              
1066 0         0 while ( read( $$handleref{jlr}, $buf, 16 ) == 16 ) {
1067 0         0 @data = unpack( "LLLL", $buf );
1068              
1069 0 0       0 if ( $data[1] == $usernum ) {
1070 0         0 %$lastreadref = ();
1071              
1072 0         0 $$lastreadref{UserCRC} = $data[0];
1073 0         0 $$lastreadref{UserID} = $data[1];
1074 0         0 $$lastreadref{LastReadMsg} = $data[2];
1075 0         0 $$lastreadref{HighReadMsg} = $data[3];
1076              
1077 0         0 return 1;
1078             }
1079             }
1080              
1081 0 0       0 if ( eof( $$handleref{jlr} ) ) {
1082 0         0 $Errnum = $FTN::JAM::Errnum::USER_NOT_FOUND;
1083             }
1084             else {
1085 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
1086             }
1087              
1088 0         0 return;
1089             }
1090              
1091             =head2 SetLastRead
1092              
1093             Syntax: $success = FTN::JAM::SetLastRead($handle,$usernum,/%lastread)
1094              
1095             =cut
1096              
1097             sub SetLastRead {
1098 0 0   0 1 0 if ( $#_ != 2 ) {
1099 0         0 croak "Wrong number of arguments for FTN::JAM::SetLastRead";
1100             }
1101              
1102 0         0 my $handleref = $_[0];
1103 0         0 my $usernum = $_[1];
1104 0         0 my $lastreadref = $_[2];
1105              
1106 0 0       0 if ( !defined( $$lastreadref{UserCRC} ) ) { $$lastreadref{UserCRC} = 0; }
  0         0  
1107 0 0       0 if ( !defined( $$lastreadref{UserID} ) ) { $$lastreadref{UserID} = 0; }
  0         0  
1108 0 0       0 if ( !defined( $$lastreadref{LastReadMsg} ) ) {
1109 0         0 $$lastreadref{LastReadMsg} = 0;
1110             }
1111 0 0       0 if ( !defined( $$lastreadref{HighReadMsg} ) ) {
1112 0         0 $$lastreadref{HighReadMsg} = 0;
1113             }
1114              
1115 0 0       0 if ( !seek( $$handleref{jlr}, 0, 0 ) ) {
1116 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
1117 0         0 return;
1118             }
1119              
1120 0         0 my $buf;
1121             my @data;
1122              
1123 0         0 while ( read( $$handleref{jlr}, $buf, 16 ) == 16 ) {
1124 0         0 @data = unpack( "LLLL", $buf );
1125              
1126 0 0       0 if ( $data[1] == $usernum ) {
1127 0 0       0 if ( !seek( $$handleref{jlr}, -16, 1 ) ) {
1128 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
1129 0         0 return;
1130             }
1131              
1132 0         0 my $printres = print { $$handleref{jlr} } pack( "LLLL",
  0         0  
1133             $$lastreadref{UserCRC}, $$lastreadref{UserID},
1134             $$lastreadref{LastReadMsg}, $$lastreadref{HighReadMsg} );
1135              
1136 0 0       0 if ( !$printres ) {
1137 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
1138 0         0 return;
1139             }
1140              
1141 0         0 return 1;
1142             }
1143             }
1144              
1145 0 0       0 if ( !eof( $$handleref{jlr} ) ) {
1146 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
1147             }
1148              
1149 0         0 my $printres = print { $$handleref{jlr} } pack( "LLLL",
  0         0  
1150             $$lastreadref{UserCRC}, $$lastreadref{UserID},
1151             $$lastreadref{LastReadMsg}, $$lastreadref{HighReadMsg} );
1152              
1153 0 0       0 if ( !$printres ) {
1154 0         0 $Errnum = $FTN::JAM::Errnum::IO_ERROR;
1155 0         0 return;
1156             }
1157              
1158 0         0 return 1;
1159             }
1160              
1161             =head2 TimeToLocal
1162              
1163             Syntax $local = FTN::JAM::TimeToLocal($time)
1164              
1165             =cut
1166              
1167             sub TimeToLocal {
1168 1 50   1 1 21 if ( $#_ != 0 ) {
1169 0         0 croak "Wrong number of arguments for FTN::JAM::TimeToLocal";
1170             }
1171              
1172 1         11 return $_[0] - timegm( 0, 0, 0, 1, 0, 70 ) + tz_local_offset();
1173             }
1174              
1175             =head2 LocalToTime
1176              
1177             Syntax $time = FTN::JAM::LocalToTime($local)
1178              
1179             =cut
1180              
1181             sub LocalToTime {
1182 0 0   0 1   if ( $#_ != 0 ) {
1183 0           croak "Wrong number of arguments for FTN::JAM::LocalToTime";
1184             }
1185              
1186 0           return $_[0] + timegm( 0, 0, 0, 1, 0, 70 ) - tz_local_offset();
1187             }
1188              
1189             =head1 AUTHOR
1190              
1191             Robert James Clay, C<< >>
1192              
1193             =head1 BUGS
1194              
1195             Please report any bugs or feature requests to C, or through the web
1196             interface at L. I will be notified, and
1197             then you'll automatically be notified of progress on your bug as I make changes.
1198              
1199             =head1 SUPPORT
1200              
1201             You can find documentation for this module with the perldoc command.
1202              
1203             perldoc FTN::JAM
1204              
1205             You can also look for information at:
1206              
1207             =over 4
1208              
1209             =item * FTN::JAM Home Page
1210              
1211             L
1212              
1213             =item * Browse the FTN::JAM GIT repository at SourceForge
1214              
1215             L
1216              
1217             =item * RT: CPAN's request tracker
1218              
1219             L
1220              
1221             =item * AnnoCPAN: Annotated CPAN documentation
1222              
1223             L
1224              
1225             =item * CPAN Ratings
1226              
1227             L
1228              
1229             =item * Search CPAN
1230              
1231             L
1232              
1233             =back
1234              
1235             =head1 ACKNOWLEDGEMENTS
1236              
1237             Originally based on the public domain Perl::JAM module by Johan Billing, which
1238             can be found at L.
1239              
1240             =head1 SEE ALSO
1241              
1242             L, L
1243              
1244             =head1 COPYRIGHT & LICENSE
1245              
1246             Copyright 2010-2012 Robert James Clay, all rights reserved.
1247              
1248             This program is free software; you can redistribute it and/or modify it
1249             under the same terms as Perl itself.
1250              
1251             =cut
1252              
1253             1; # End of FTN::JAM