File Coverage

blib/lib/IMDB/Local.pm
Criterion Covered Total %
statement 24 898 2.6
branch 0 484 0.0
condition 0 104 0.0
subroutine 7 39 17.9
pod 6 25 24.0
total 37 1550 2.3


line stmt bran cond sub pod time code
1             package IMDB::Local;
2              
3             #
4             # Suggestions for improvements
5             # -
6             #
7             #
8              
9 1     1   13813 use 5.006;
  1         3  
  1         30  
10 1     1   4 use strict;
  1         1  
  1         30  
11 1     1   3 use warnings;
  1         4  
  1         58  
12              
13             =head1 NAME
14              
15             IMDB::Local - Tools to dowload and manage a local copy of the IMDB list files in a database.
16              
17             =cut
18              
19             our $VERSION = '1.5';
20              
21             =head1 SYNOPSIS
22              
23             Quick summary of what the module does.
24              
25             Perhaps a little code snippet.
26              
27             my $foo = new IMDB::Local('imdbDir' => "./imdb-data",
28             'listsDir' => "./imdb-data/lists",
29             'showProgressBar' => 1);
30              
31             for my $type ( $foo->listTypes() ) {
32             if ( $foo->importList($type) != 0 ) {
33             warn("$type import failed, check $foo->{imdbDir}/stage-$type.log");
34             }
35             }
36             ...
37              
38             =head1 EXPORT
39              
40             A list of functions that can be exported. You can delete this section
41             if you don't export anything, such as for a purely object-oriented module.
42              
43             =head1 SUBROUTINES/METHODS
44              
45             =cut
46              
47             # Use Term::ProgressBar if installed.
48 1         1 use constant Have_bar => eval {
49 1         434 require Term::ProgressBar;
50 1         53199 $Term::ProgressBar::VERSION >= 2;
51 1     1   4 };
  1         3  
52              
53 1     1   462 use IMDB::Local::DB;
  1         3  
  1         29  
54 1     1   313 use IMDB::Local::QualifierType ':types';
  1         2  
  1         4  
55              
56             =head2 new
57              
58             Create new IMDB::Local object.
59              
60             Arguments:
61              
62             imdbDir - required or die
63              
64             verbose - optional, default is 0.
65              
66             listsDir - folder where list files exist (see IMDB::Local::Download).
67              
68             showProgressBar - if non-zero and Term::ProgressBar is available progress bars in import methods will be displayed. Ignored if Term::ProgressBar is not available.
69              
70             =cut
71              
72             sub new
73             {
74 0     0 1   my ($type) = shift;
75 0           my $self={ @_ }; # remaining args become attributes
76              
77 0           for ('imdbDir', 'verbose') {
78 0 0         die "invalid usage - no $_" if ( !defined($self->{$_}));
79             }
80            
81             #$self->{stages} = { 1=>'movies', 2=>'directors', 3=>'actors', 4=>'actresses', 5=>'genres', 6=>'ratings', 7=>'keywords', 8=>'plot' };
82             #$self->{optionalStages} = { 'keywords' => 7, 'plot' => 8 }; # list of optional stages - no need to download files for these
83              
84 0           $self->{moviedbInfo}="$self->{imdbDir}/moviedb.info";
85 0           $self->{moviedbOffline}="$self->{imdbDir}/moviedb.offline";
86            
87 0 0         if ( defined($self->{listsDir}) ) {
88 0           $self->{listFiles}=new IMDB::Local::ListFiles(listsDir=>$self->{listsDir});
89             }
90            
91             # only leave progress bar on if its available
92 0           if ( !Have_bar ) {
93             $self->{showProgressBar}=0;
94             }
95              
96 0           bless($self, $type);
97 0           return($self);
98             }
99              
100             #sub openDB
101             #{
102             # my ($self)=@_;
103             #
104             # my $DB=new IMDB::Local::DB(database=>"$self->{imdbDir}/imdb.db");
105             #
106             # if ( !$DB->connect() ) {
107             # carp "imdbdb connect failed:$DBI::errstr";
108             # }
109             # $self->{DB}=$DB;
110             #
111             # return($DB);
112             #}
113             #
114             #sub closeDB
115             #{
116             # my ($self)=@_;
117             #
118             # $self->{DB}->disconnect();
119             # undef $self->{DB};
120             #}
121              
122             =head2 listTypes
123              
124             Returns an array of list files supported (currently 'movies', 'directors', 'actors', 'actresses', 'genres', 'ratings', 'keywords', 'plot')
125              
126             =cut
127              
128             sub listTypes($)
129             {
130 0     0 1   my $self=shift;
131              
132 0           return( $self->{listFiles}->types() );
133             }
134              
135              
136             sub error($$)
137             {
138 0     0 0   my $self=shift;
139 0 0         if ( defined($self->{logfd}) ) {
140 0           print {$self->{logfd}} $_[0]."\n";
  0            
141 0           $self->{errorCountInLog}++;
142             }
143             else {
144 0           print STDERR $_[0]."\n";
145             }
146             }
147              
148             sub status($$)
149             {
150 0     0 0   my $self=shift;
151              
152 0 0         if ( $self->{verbose} ) {
153 0           print STDERR $_[0]."\n";
154             }
155             }
156              
157             sub withThousands ($)
158             {
159 0     0 0   my ($val) = @_;
160 0           $val =~ s/(\d{1,3}?)(?=(\d{3})+$)/$1,/g;
161 0           return $val;
162             }
163              
164 1         2 use constant Have_gunzip => eval {
165 1         8897 require IO::Uncompress::Gunzip;
166 1     1   446 };
  1         1  
167              
168              
169             sub openMaybeGunzip($)
170             {
171 0     0 0   my ($file)=@_;
172            
173 0 0         if ($file=~m/\.gz$/ ) {
174 0           if ( Have_gunzip ) {
175 0           return new IO::Uncompress::Gunzip($file);
176             }
177             else {
178             my $fd;
179              
180             if ( open($fd, "gzip -d < $file |") ) {
181             return($fd);
182             }
183             carp("no suitable gzip decompression found");
184             }
185             }
186             else {
187 0           require IO::File;
188 0           return new IO::File("< $file");
189             }
190             }
191              
192             sub closeMaybeGunzip($$)
193             {
194 0     0 0   my ($file, $fd)=@_;
195              
196 0 0         if ($file=~m/\.gz$/ ) {
197 0           if ( Have_gunzip ) {
198 0           $fd->close();
199             }
200             else {
201             close($fd);
202             }
203             }
204             else {
205 0           $fd->close();
206             }
207             }
208              
209             sub decodeImdbKey($$$)
210             {
211 0     0 0   my ($self, $DB, $dbkey, $year, $titleID)=@_;
212            
213 0           my %hash;
214            
215 0           $hash{parentId}=0;
216 0           $hash{series}=0;
217 0           $hash{episode}=0;
218 0           $hash{airdate}=0;
219            
220              
221             # drop episode information - ex: "Studio One" (1948) {Twelve Angry Men (#7.1)}
222 0 0         if ( $dbkey=~s/\s*\{([^\}]+)\}//o ) {
223 0           my $s=$1;
224 0 0         if ( $s=~s/\s*\(\#(\d+)\.(\d+)\)$// ) {
    0          
225 0           $hash{series}=$1;
226 0           $hash{episode}=$2;
227 0           $hash{title}=$s;
228             #print "title: $s\n";
229              
230             # attempt to locate parentId matching series title
231             #my $parentKey=$dbkey;
232             #$parentKey=~s/^\"//o;
233             #$parentKey=~s/\" \(/ \(/o;
234              
235             #warn("checkskey $dbkey");
236             #if ( defined($self->{seriesKeys}->{$parentKey}) ) {
237             #$hash{parentId}=$self->{seriesKeys}->{$parentKey};
238             #}
239             }
240             # "EastEnders" (1985) {(1991-10-15)} 1991
241             elsif ( $s=~m/^\((\d\d\d\d)\-(\d\d)\-(\d\d)\)$/ ) {
242 0           $hash{airdate}=int("$1$2$3");
243 0           $hash{title}=$s;
244             }
245             else {
246 0           $hash{title}=$s;
247             }
248              
249             # attempt to locate parentId matching series title
250 0           my $parentKey=$dbkey;
251 0           $parentKey=~s/^\"//o;
252 0           $parentKey=~s/\" \(/ \(/o;
253            
254             #warn("checkskey $dbkey");
255 0 0         if ( defined($self->{seriesKeys}->{$parentKey}) ) {
256 0           $hash{parentId}=$self->{seriesKeys}->{$parentKey};
257             }
258             }
259            
260            
261             # change double-quotes around title to be (made-for-tv) suffix instead
262 0 0 0       if ( $dbkey=~s/^\"//o && $dbkey=~s/\" \(/ \(/o) {
    0          
    0          
    0          
263 0 0         if ( $dbkey=~s/\s+\(mini\)$//o ) {
264 0 0         if ( $hash{parentId} == 0 ) {
265 0           $hash{qualifier}=IMDB::Local::QualifierType::TV_MINI_SERIES;
266 0           $self->{seriesKeys}->{$dbkey}=$titleID;
267             }
268             else {
269 0           $hash{qualifier}=IMDB::Local::QualifierType::EPISODE_OF_TV_MINI_SERIES
270             }
271             }
272             else {
273 0 0         if ( $hash{parentId} == 0 ) {
274 0           $hash{qualifier}=IMDB::Local::QualifierType::TV_SERIES;
275 0           $self->{seriesKeys}->{$dbkey}=$titleID;
276             }
277             else {
278 0           $hash{qualifier}=IMDB::Local::QualifierType::EPISODE_OF_TV_SERIES;
279             }
280             }
281             }
282             elsif ( $dbkey=~s/\s+\(TV\)$//o ) {
283             # how rude, some entries have (TV) appearing more than once.
284             #$dbkey=~s/\s*\(TV\)$//o;
285 0           $hash{qualifier}=IMDB::Local::QualifierType::TV_MOVIE;
286             }
287             elsif ( $dbkey=~s/\s+\(V\)$//o ) {
288 0           $hash{qualifier}=IMDB::Local::QualifierType::VIDEO_MOVIE;
289             }
290             elsif ( $dbkey=~s/\s+\(VG\)$//o ) {
291 0           $hash{qualifier}=IMDB::Local::QualifierType::VIDEO_GAME;
292             }
293             else {
294 0           $hash{qualifier}=IMDB::Local::QualifierType::MOVIE;
295             }
296            
297             #if ( $dbkey=~s/\s+\((tv_series|tv_mini_series|tv_movie|video_movie|video_game)\)$//o ) {
298             # $qualifier=$1;
299             #}
300 0           $hash{dbkey}=$dbkey;
301              
302 0           my $title=$dbkey;
303            
304             # todo - this is the wrong year for episode titles
305 0 0 0       if ( $title=~m/^\"/o && $title=~m/\"\s*\(/o ) { #"
306 0           $title=~s/^\"//o; #"
307 0           $title=~s/\"(\s*\()/$1/o; #"
308             }
309            
310 0 0 0       if ( $title=~s/\s+\((\d\d\d\d)\)$//o ||
    0 0        
311             $title=~s/\s+\((\d\d\d\d)\/[IVXL]+\)$//o ) {
312             # over-ride with what is given
313 0 0         if ( !defined($year) ) {
314 0           $hash{year}=$1;
315             }
316             else {
317 0           $hash{year}=$year;
318             }
319             }
320             elsif ( $title=~s/\s+\((\?\?\?\?)\)$//o ||
321             $title=~s/\s+\((\?\?\?\?)\/[IVXL]+\)$//o ) {
322             # over-ride with what is given
323 0 0         if ( !defined($year) ) {
324 0           $hash{year}=0;
325             }
326             else {
327 0           $hash{year}=$year;
328             }
329             }
330             else {
331 0           $self->error("movie list format failed to decode year from title '$title'");
332            
333             # over-ride with what is given
334 0 0         if ( ! defined($year) ) {
335 0           $hash{year}=0;
336             }
337             else {
338 0           $hash{year}=$year;
339             }
340             }
341 0           $title=~s/(.*),\s*(The|A|Une|Las|Les|Los|L\'|Le|La|El|Das|De|Het|Een)$/$2 $1/og;
342              
343             # leave searchtitle empty for tv series'
344 0 0 0       if( $hash{qualifier} == IMDB::Local::QualifierType::EPISODE_OF_TV_SERIES ||
345             $hash{qualifier} == IMDB::Local::QualifierType::EPISODE_OF_TV_MINI_SERIES ) {
346             #$hash{title}=$title;
347 0           $hash{searchTitle}=$DB->makeSearchableTitle($hash{title}, 0);;
348             }
349             else {
350 0 0         if ( !defined($hash{title}) ) {
351 0           $hash{title}=$title;
352 0           $hash{searchTitle}=$DB->makeSearchableTitle($title, 0);
353              
354             # todo - is this more useful ?
355             #$hash{searchTitleWithYear}=MakeSearchtitle($DB, $title."(".$hash{year}.")", 0);
356             }
357             else {
358 0           $hash{searchTitle}=$DB->makeSearchableTitle($title, 0);
359             #$hash{searchTitle}=$DB->makeSearchableTitle($title."(".$hash{year}.")", 0);
360            
361             # todo - is this more useful ?
362             #$hash{searchTitleWithYear}=$DB->makeSearchableTitle($title."(".$hash{year}.")", 0);
363             }
364             }
365              
366 0           return(\%hash);
367             }
368              
369             sub importMovies($$$$)
370             {
371 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
372 0           my $startTime=time();
373 0           my $lineCount=0;
374              
375 0   0       my $fh = openMaybeGunzip($file) || return(-2);
376 0           while(<$fh>) {
377 0           $lineCount++;
378 0 0         if ( m/^MOVIES LIST/o ) {
    0          
379 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
380 0           $self->error("missing ======= after 'MOVIES LIST' at line $lineCount");
381 0           closeMaybeGunzip($file, $fh);
382 0           return(-1);
383             }
384 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
385 0           $self->error("missing empty line after ======= at line $lineCount");
386 0           closeMaybeGunzip($file, $fh);
387 0           return(-1);
388             }
389 0           last;
390             }
391             elsif ( $lineCount > 1000 ) {
392 0           $self->error("$file: stopping at line $lineCount, didn't see \"MOVIES LIST\" line");
393 0           closeMaybeGunzip($file, $fh);
394 0           return(-1);
395             }
396             }
397              
398 0 0         my $progress=Term::ProgressBar->new({name => "importing Movies",
399             count => $countEstimate,
400             ETA => 'linear'})
401             if ( $self->{showProgressBar} );
402              
403 0 0         $progress->minor(0) if ($self->{showProgressBar});
404 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
405 0           my $next_update=0;
406              
407             #$DB->runSQL("BEGIN TRANSACTION");
408              
409 0           my $count=0;
410 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Titles (TitleID, SearchTitle, Title, QualifierTypeID, Year, ParentID, Series, Episode, AirDate) VALUES (?,?,?,?,?,?,?,?,?)');
411              
412 0           my $potentialEntries=0;
413            
414 0           while(<$fh>) {
415 0           $lineCount++;
416 0           my $line=$_;
417              
418             # end is line consisting of only '-'
419 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
420              
421 0 0         next if ( $line=~m/\{\{SUSPENDED\}\}/o );
422 0           $line=~s/\n$//o;
423            
424             #next if ( !($line=~m/biography/io) );
425              
426             #print "read line $lineCount:$line\n";
427 0           $potentialEntries++;
428              
429 0           my $tab=index($line, "\t");
430 0 0         if ( $tab != -1 ) {
431 0           my $ykey=substr($line, $tab+1);
432 0 0         if ( $ykey=m/\s+(\d\d\d\d)$/ ) {
    0          
    0          
    0          
433 0           $ykey=$1;
434             }
435             elsif ( $ykey=m/\s+(\?\?\?\?)$/ ) {
436 0           $ykey=undef;
437             }
438             elsif ( $ykey=m/\s+(\d\d\d\d)\-(\?\?\?\?)$/ ) {
439 0           $ykey=$1;
440             }
441             elsif ( $ykey=m/\s+(\d\d\d\d)\-(\d\d\d\d)$/ ) {
442 0           $ykey=$1;
443             }
444             else {
445 0           warn("invalid year ($ykey) - $line");
446             #$ykey=undef;
447             }
448            
449 0           my $mkey=substr($line, 0, $tab);
450              
451             # returned count is number of titles found
452 0           $count++;
453              
454 0           my $decoded=$self->decodeImdbKey($DB, $mkey, $ykey, $count);
455              
456 0           $tableInsert_sth->execute($count,
457             $decoded->{searchTitle},
458             $decoded->{title},
459             $decoded->{qualifier},
460             $decoded->{year},
461             $decoded->{parentId},
462             $decoded->{series},
463             $decoded->{episode},
464             $decoded->{airdate});
465            
466 0           $self->{imdbMovie2DBKey}->{$mkey}=$count;
467              
468             #if ( ($count % 50000) == 0 ) {
469             #$DB->commit();
470             #}
471             #}
472            
473 0 0         if ( $self->{showProgressBar} ) {
474             # re-adjust target so progress bar doesn't seem too wonky
475 0 0         if ( $count > $countEstimate ) {
    0          
476 0           $countEstimate = $progress->target($count+1000);
477 0           $next_update=$progress->update($count);
478             }
479             elsif ( $count > $next_update ) {
480 0           $next_update=$progress->update($count);
481             }
482             }
483             }
484             else {
485 0           $self->error("$file:$lineCount: unrecognized format (missing tab)");
486             }
487             }
488             #$DB->runSQL("END TRANSACTION");
489              
490 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
491              
492 0           $self->status(sprintf("importing Movies found ".withThousands($count)." in ".
493             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
494              
495 0           closeMaybeGunzip($file, $fh);
496 0           $DB->commit();
497 0           return($count);
498             }
499              
500             sub importGenres($$$$)
501             {
502 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
503 0           my $startTime=time();
504 0           my $lineCount=0;
505              
506 0   0       my $fh = openMaybeGunzip($file) || return(-2);
507 0           while(<$fh>) {
508 0           $lineCount++;
509 0 0         if ( m/^8: THE GENRES LIST/o ) {
    0          
510 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
511 0           $self->error("missing ======= after 'THE GENRES LIST' at line $lineCount");
512 0           closeMaybeGunzip($file, $fh);
513 0           return(-1);
514             }
515 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
516 0           $self->error("missing empty line after ======= at line $lineCount");
517 0           closeMaybeGunzip($file, $fh);
518 0           return(-1);
519             }
520 0           last;
521             }
522             elsif ( $lineCount > 1000 ) {
523 0           $self->error("$file: stopping at line $lineCount, didn't see \"THE GENRES LIST\" line");
524 0           closeMaybeGunzip($file, $fh);
525 0           return(-1);
526             }
527             }
528              
529 0 0         my $progress=Term::ProgressBar->new({name => "importing Genres",
530             count => $countEstimate,
531             ETA => 'linear'})
532             if ( $self->{showProgressBar} );
533              
534 0 0         $progress->minor(0) if ($self->{showProgressBar});
535 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
536 0           my $next_update=0;
537              
538             #$DB->runSQL("BEGIN TRANSACTION");
539              
540 0           my $count=0;
541 0           my $potentialEntries=0;
542 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Titles2Genres (TitleID, GenreID) VALUES (?,?)');
543              
544 0           while(<$fh>) {
545 0           $lineCount++;
546 0           my $line=$_;
547             #print "read line $lineCount:$line\n";
548              
549             # end is line consisting of only '-'
550 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
551 0 0         next if ( $line=~m/\s*\{\{SUSPENDED\}\}/o);
552              
553 0           $potentialEntries++;
554              
555 0           $line=~s/\n$//o;
556              
557 0           my $tab=index($line, "\t");
558 0 0         if ( $tab != -1 ) {
559 0           my $mkey=substr($line, 0, $tab);
560              
561             # ignore {Twelve Angry Men (1954)}
562             # TODO - do we want this ?
563             #$mkey=~s/\s*\{[^\}]+\}//go;
564            
565             # skip enties that have {} in them since they're tv episodes
566             #next if ( $mkey=~s/\s*\{[^\}]+\}$//o );
567            
568 0           my $genre=substr($line, $tab);
569            
570             # genres sometimes has more than one tab
571 0           $genre=~s/^\t+//og;
572            
573 0 0         if ( $self->{imdbMovie2DBKey}->{$mkey} ) {
574             # insert into db as discovered
575 0 0         if ( ! defined($self->{GenreID}->{$genre}) ) {
576 0           $self->{GenreID}->{$genre}=$DB->insert_row('Genres', 'GenreID', Name=>$genre);
577             }
578 0           $tableInsert_sth->execute($self->{imdbMovie2DBKey}->{$mkey},
579             $self->{GenreID}->{$genre});
580            
581             # returned count is number of titles found
582 0           $count++;
583            
584 0 0         if ( ($count % 50000) ==0 ) {
585 0           $DB->commit();
586             }
587             }
588            
589 0 0         if ( $self->{showProgressBar} ) {
590             # re-adjust target so progress bar doesn't seem too wonky
591 0 0         if ( $count > $countEstimate ) {
    0          
592 0           $countEstimate = $progress->target($count+1000);
593 0           $next_update=$progress->update($count);
594             }
595             elsif ( $count > $next_update ) {
596 0           $next_update=$progress->update($count);
597             }
598             }
599             }
600             else {
601 0           $self->error("$file:$lineCount: unrecognized format (missing tab)");
602             }
603             }
604             #$DB->runSQL("END TRANSACTION");
605              
606 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
607              
608 0           $self->status(sprintf("importing Genres found ".withThousands($count)." in ".
609             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
610              
611 0           closeMaybeGunzip($file, $fh);
612 0           $DB->commit();
613 0           return($count);
614             }
615              
616             sub importActors($$$$)
617             {
618 0     0 0   my ($self, $whichCastType, $castCountEstimate, $file, $DB)=@_;
619 0           my $startTime=time();
620              
621 0 0         if ( $whichCastType eq "Actors" ) {
622 0 0 0       if ( $DB->table_row_count('Actors') > 0 ||
      0        
      0        
623             $DB->table_row_count('Titles2Actors') > 0 ||
624             $DB->table_row_count('Titles2Hosts') > 0 ||
625             $DB->table_row_count('Titles2Narrators') > 0 ) {
626 0           $self->status("clearing previously loaded data..");
627 0           $DB->table_clear('Actors');
628 0           $DB->table_clear('Titles2Actors');
629 0           $DB->table_clear('Titles2Hosts');
630 0           $DB->table_clear('Titles2Narrators');
631             }
632             }
633              
634 0           my $header;
635             my $whatAreWeParsing;
636 0           my $lineCount=0;
637              
638 0 0         if ( $whichCastType eq "Actors" ) {
    0          
639 0           $header="THE ACTORS LIST";
640 0           $whatAreWeParsing=1;
641             }
642             elsif ( $whichCastType eq "Actresses" ) {
643 0           $header="THE ACTRESSES LIST";
644 0           $whatAreWeParsing=2;
645             }
646             else {
647 0           die "why are we here ?";
648             }
649              
650 0   0       my $fh = openMaybeGunzip($file) || return(-2);
651 0 0         my $progress=Term::ProgressBar->new({name => "importing $whichCastType",
652             count => $castCountEstimate,
653             ETA => 'linear'})
654             if ($self->{showProgressBar});
655 0 0         $progress->minor(0) if ($self->{showProgressBar});
656 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
657 0           my $next_update=0;
658            
659 0           while(<$fh>) {
660 0           $lineCount++;
661 0 0         if ( m/^$header/ ) {
    0          
662 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
663 0           $self->error("missing ======= after $header at line $lineCount");
664 0           closeMaybeGunzip($file, $fh);
665 0           return(-1);
666             }
667 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
668 0           $self->error("missing empty line after ======= at line $lineCount");
669 0           closeMaybeGunzip($file, $fh);
670 0           return(-1);
671             }
672 0 0 0       if ( !($_=<$fh>) || !m/^Name\s+Titles\s*$/o ) {
673 0           $self->error("missing name/titles line after ======= at line $lineCount");
674 0           closeMaybeGunzip($file, $fh);
675 0           return(-1);
676             }
677 0 0 0       if ( !($_=<$fh>) || !m/^[\s\-]+$/o ) {
678 0           $self->error("missing name/titles suffix line after ======= at line $lineCount");
679 0           closeMaybeGunzip($file, $fh);
680 0           return(-1);
681             }
682 0           last;
683             }
684             elsif ( $lineCount > 1000 ) {
685 0           $self->error("$file: stopping at line $lineCount, didn't see \"$header\" line");
686 0           closeMaybeGunzip($file, $fh);
687 0           return(-1);
688             }
689             }
690              
691 0           my $cur_name;
692 0           my $count=0;
693 0           my $castNames=0;
694 0           my $tableInsert_sth1=$DB->prepare('INSERT INTO Actors (ActorID, SearchName, Name) VALUES (?,?,?)');
695 0           my $tableInsert_sth2=$DB->prepare('INSERT INTO Titles2Hosts (TitleID, ActorID) VALUES (?,?)');
696 0           my $tableInsert_sth3=$DB->prepare('INSERT INTO Titles2Narrators (TitleID, ActorID) VALUES (?,?)');
697 0           my $tableInsert_sth4=$DB->prepare('INSERT INTO Titles2Actors (TitleID, ActorID, Billing) VALUES (?,?,?)');
698            
699 0           my $cur_actorId=$DB->select2Scalar('Select MAX(ActorID) from Actors');
700 0 0         if ( !defined($cur_actorId) ) {
701 0           $cur_actorId=0;
702             }
703              
704 0           my $potentialEntries=0;
705 0           while(<$fh>) {
706 0           $lineCount++;
707 0           my $line=$_;
708 0           $line=~s/\n$//o;
709             #$self->status("read line $lineCount:$line");
710              
711             # end is line consisting of only '-'
712 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
713            
714 0 0         next if ( length($line) == 0 );
715              
716             # try ignoring these
717 0 0         next if ($line=~m/\s*\{\{SUSPENDED\}\}/o);
718              
719 0           $potentialEntries++;
720 0           my $billing=9999;
721            
722             # actors or actresses
723 0 0         if ( $line=~s/\s*<(\d+)>//o ) {
724 0           $billing=int($1);
725 0 0         next if ( $billing >3 );
726             }
727            
728 0 0         if ( $line=~s/^([^\t]+)\t+//o ) {
729 0           $cur_name=$1;
730 0           $castNames++;
731              
732 0           $cur_actorId++;
733              
734 0           my $c=$cur_name;
735 0           $c=~s/\s*\([IVXL]+\)//o;
736 0           $tableInsert_sth1->execute($cur_actorId, $DB->makeSearchableTitle($c, 0), $cur_name);
737              
738 0 0         if ( $self->{showProgressBar} ) {
739             # re-adjust target so progress bar doesn't seem too wonky
740 0 0         if ( $castNames > $castCountEstimate ) {
    0          
741 0           $castCountEstimate = $progress->target($castNames+100);
742 0           $next_update=$progress->update($castNames);
743             }
744             elsif ( $castNames > $next_update ) {
745 0           $next_update=$progress->update($castNames);
746             }
747             }
748             }
749            
750 0           my $isHost=0;
751 0           my $isNarrator=0;
752 0 0         if ( (my $start=index($line, " [")) != -1 ) {
753             #my $end=rindex($line, "]");
754 0           my $ex=substr($line, $start+1);
755            
756 0 0         if ( $ex=~s/Host//o ) {
757 0           $isHost=1;
758             }
759 0 0         if ( $ex=~s/Narrator//o ) {
760 0           $isNarrator=1;
761             }
762 0           $line=substr($line, 0, $start);
763             # ignore character name
764             }
765            
766             # TODO - do we want to just ignore these ?
767 0 0         if ( $line=~s/\s*\(aka ([^\)]+)\).*$//o ) {
768             #$attrs=$1;
769             }
770            
771             # TODO - what are we ignoring here ?
772 0 0         if ( $line=~s/ (\(.*)$//o ) {
773             #$attrs=$1;
774             }
775 0           $line=~s/^\s+//og;
776 0           $line=~s/\s+$//og;
777              
778             # TODO - does this exist ?
779 0 0         if ( $line=~s/\s+Narrator$//o ) {
780 0           $self->error("extra narrator on line: $lineCount");
781             # TODO - do we want to store this ? Does it actually occur ?
782             # ignore
783             }
784              
785             #if ( $line=~s/\s*\([A-Z]+\)$//o ) {
786             #}
787              
788 0           my $titleID=$self->{imdbMovie2DBKey}->{$line};
789 0 0         if ( $titleID ) {
790 0 0         if ( $isHost ) {
791 0           $tableInsert_sth2->execute($titleID, $cur_actorId);
792             }
793 0 0         if ( $isNarrator ) {
794 0           $tableInsert_sth3->execute($titleID, $cur_actorId);
795             }
796 0 0 0       if ( !$isHost && !$isNarrator ) {
797 0           $tableInsert_sth4->execute($titleID, $cur_actorId, $billing);
798             }
799            
800 0           $count++;
801 0 0         if ( ($count % 50000) == 0 ) {
802 0           $DB->commit();
803             }
804             }
805             else {
806             #warn($line);
807             }
808             }
809 0 0         $progress->update($castCountEstimate) if ($self->{showProgressBar});
810              
811 0           $self->status(sprintf("importing $whichCastType found ".withThousands($castNames)." names, ".
812             withThousands($count)." titles in ".withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
813            
814 0           closeMaybeGunzip($file, $fh);
815              
816 0           $DB->commit();
817 0           return($castNames);
818             }
819              
820             sub importDirectors($$$)
821             {
822 0     0 0   my ($self, $castCountEstimate, $file, $DB)=@_;
823 0           my $startTime=time();
824              
825 0           my $lineCount=0;
826              
827 0 0 0       if ( $DB->table_row_count('Directors') > 0 ||
828             $DB->table_row_count('Titles2Directors') > 0 ) {
829 0           $self->status("clearing previously loaded data..");
830 0           $DB->table_clear('Directors');
831 0           $DB->table_clear('Titles2Directors');
832             }
833              
834 0   0       my $fh = openMaybeGunzip($file) || return(-2);
835 0 0         my $progress=Term::ProgressBar->new({name => "importing Directors",
836             count => $castCountEstimate,
837             ETA => 'linear'})
838             if ($self->{showProgressBar});
839 0 0         $progress->minor(0) if ($self->{showProgressBar});
840 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
841 0           my $next_update=0;
842 0           while(<$fh>) {
843 0           $lineCount++;
844 0 0         if ( m/^THE DIRECTORS LIST/ ) {
    0          
845 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
846 0           $self->error("missing ======= after THE DIRECTORS LIST at line $lineCount");
847 0           closeMaybeGunzip($file, $fh);
848 0           return(-1);
849             }
850 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
851 0           $self->error("missing empty line after ======= at line $lineCount");
852 0           closeMaybeGunzip($file, $fh);
853 0           return(-1);
854             }
855 0 0 0       if ( !($_=<$fh>) || !m/^Name\s+Titles\s*$/o ) {
856 0           $self->error("missing name/titles line after ======= at line $lineCount");
857 0           closeMaybeGunzip($file, $fh);
858 0           return(-1);
859             }
860 0 0 0       if ( !($_=<$fh>) || !m/^[\s\-]+$/o ) {
861 0           $self->error("missing name/titles suffix line after ======= at line $lineCount");
862 0           closeMaybeGunzip($file, $fh);
863 0           return(-1);
864             }
865 0           last;
866             }
867             elsif ( $lineCount > 1000 ) {
868 0           $self->error("$file: stopping at line $lineCount, didn't see \"THE DIRECTORS LIST\" line");
869 0           closeMaybeGunzip($file, $fh);
870 0           return(-1);
871             }
872             }
873              
874 0           my $cur_name;
875 0           my $count=0;
876 0           my $castNames=0;
877 0           my %found;
878 0           my $directorCount=0;
879 0           my $potentialEntries=0;
880              
881 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Directors (DirectorID, SearchName, Name) VALUES (?,?,?)');
882 0           my $tableInsert_sth2=$DB->prepare('INSERT INTO Titles2Directors (TitleID, DirectorID) VALUES (?,?)');
883 0           while(<$fh>) {
884 0           $lineCount++;
885            
886             #last if ( $lineCount > 10000);
887 0           my $line=$_;
888 0           $line=~s/\n$//o;
889             #$self->status("read line $lineCount:$line");
890              
891             # end is line consisting of only '-'
892 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
893 0 0         next if ( length($line) == 0 );
894            
895             # try ignoring these
896 0 0         next if ($line=~m/\s*\{\{SUSPENDED\}\}/o);
897            
898 0           $potentialEntries++;
899            
900 0 0         if ( $line=~s/^([^\t]+)\t+//o ) {
901 0           $cur_name=$1;
902 0           $castNames++;
903              
904 0 0         if ( $self->{showProgressBar} ) {
905             # re-adjust target so progress bar doesn't seem too wonky
906 0 0         if ( $castNames > $castCountEstimate ) {
    0          
907 0           $castCountEstimate = $progress->target($castNames+100);
908 0           $next_update=$progress->update($castNames);
909             }
910             elsif ( $castNames > $next_update ) {
911 0           $next_update=$progress->update($castNames);
912             }
913             }
914             }
915            
916             # BUG
917             # ##ignore {Twelve Angry Men (1954)}
918             #$line=~s/\s*\{[^\}]+\}//o;
919              
920             # sometimes there are extra bits of info attached at the end of lines, we'll ignore these
921             #
922             # examples:
923             # Deszcz (1997) (as Tomasz Baginski)
924             # Adventures of Modest Mouse (2008) (co-director)
925             # Vida (2010) (collaborating director)
926             # Rex Harrison Presents Stories of Love (1974) (TV) (segment "Epicac")
927 0 0         if ( $line=~s/ (\(.*)$//o ) {
928             # $attrs=$1;
929             }
930            
931 0           $line=~s/^\s+//og;
932 0           $line=~s/\s+$//og;
933              
934 0 0         if ( $self->{imdbMovie2DBKey}->{$line} ) {
935              
936 0 0         if ( !defined($found{$cur_name}) ) {
937 0           $directorCount++;
938 0           $found{$cur_name}=$directorCount;
939              
940 0           my $c=$cur_name;
941 0           $c=~s/\s*\([IVXL]+\)//o;
942 0           $tableInsert_sth->execute($directorCount, $DB->makeSearchableTitle($c, 0), $cur_name);
943             }
944              
945 0           $tableInsert_sth2->execute($self->{imdbMovie2DBKey}->{$line}, $found{$cur_name});
946 0           $count++;
947 0 0         if ( ($count % 50000) == 0 ) {
948 0           $DB->commit();
949             }
950             }
951             else {
952 0           $self->error("$lineCount: unable to match title key '$line'");
953             }
954             }
955 0 0         $progress->update($castCountEstimate) if ($self->{showProgressBar});
956              
957 0           $self->status(sprintf("importing Directors found ".withThousands($castNames)." names, ".
958             withThousands($count)." titles in ".withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
959            
960 0           closeMaybeGunzip($file, $fh);
961              
962 0           $DB->commit();
963 0           return($castNames);
964             }
965              
966             sub importRatings($$)
967             {
968 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
969 0           my $startTime=time();
970 0           my $lineCount=0;
971              
972 0 0         if ( $DB->table_row_count('Ratings') > 0 ) {
973 0           $self->status("clearing previously loaded data..");
974 0           $DB->table_clear('Ratings');
975             }
976              
977 0   0       my $fh = openMaybeGunzip($file) || return(-2);
978 0           while(<$fh>) {
979 0           $lineCount++;
980 0 0         if ( m/^MOVIE RATINGS REPORT/o ) {
    0          
981 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o) {
982 0           $self->error("missing empty line after \"MOVIE RATINGS REPORT\" at line $lineCount");
983 0           closeMaybeGunzip($file, $fh);
984 0           return(-1);
985             }
986 0 0 0       if ( !($_=<$fh>) || !m/^New Distribution Votes Rank Title/o ) {
987 0           $self->error("missing \"New Distribution Votes Rank Title\" at line $lineCount");
988 0           closeMaybeGunzip($file, $fh);
989 0           return(-1);
990             }
991 0           last;
992             }
993             elsif ( $lineCount > 1000 ) {
994 0           $self->error("$file: stopping at line $lineCount, didn't see \"MOVIE RATINGS REPORT\" line");
995 0           closeMaybeGunzip($file, $fh);
996 0           return(-1);
997             }
998             }
999              
1000 0 0         my $progress=Term::ProgressBar->new({name => "importing Ratings",
1001             count => $countEstimate,
1002             ETA => 'linear'})
1003             if ($self->{showProgressBar});
1004              
1005 0 0         $progress->minor(0) if ($self->{showProgressBar});
1006 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
1007 0           my $next_update=0;
1008              
1009 0           my $countImported=0;
1010 0           my $count=0;
1011 0           my $potentialEntries=0;
1012 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Ratings (TitleID, Distribution, Votes, Rank) VALUES (?,?,?,?)');
1013 0           while(<$fh>) {
1014 0           $lineCount++;
1015 0           my $line=$_;
1016             #print "read line $lineCount:$line";
1017              
1018 0           $line=~s/\n$//o;
1019            
1020             # skip empty lines (only really appear right before last line ending with ----
1021 0 0         next if ( $line=~m/^\s*$/o );
1022             # end is line consisting of only '-'
1023 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
1024              
1025 0           $potentialEntries++;
1026            
1027             # e.g. New Distribution Votes Rank Title
1028             # 0000000133 225568 8.9 12 Angry Men (1957)
1029 0 0         if ( $line=~m/^\s+([\.|\*|\d]+)\s+(\d+)\s+(\d+\.\d+)\s+(.+)$/o ) {
1030              
1031 0 0         if ( $self->{imdbMovie2DBKey}->{$4} ) {
1032 0           $tableInsert_sth->execute($self->{imdbMovie2DBKey}->{$4}, $1, $2, $3);
1033 0           $countImported++;
1034 0 0         if ( ($countImported % 50000) == 0 ) {
1035 0           $DB->commit();
1036             }
1037             }
1038            
1039 0           $count++;
1040              
1041             #$self->{movies}{$line}=[$1,$2,"$3.$4"];
1042 0 0         if ( $self->{showProgressBar} ) {
1043             # re-adjust target so progress bar doesn't seem too wonky
1044 0 0         if ( $count > $countEstimate ) {
    0          
1045 0           $countEstimate = $progress->target($count+1000);
1046 0           $next_update=$progress->update($count);
1047             }
1048             elsif ( $count > $next_update ) {
1049 0           $next_update=$progress->update($count);
1050             }
1051             }
1052             }
1053             else {
1054 0           $self->error("$file:$lineCount: unrecognized format");
1055             }
1056             }
1057 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
1058              
1059 0           $self->status(sprintf("importing Ratings found ".withThousands($count)." in ".
1060             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
1061            
1062 0           closeMaybeGunzip($file, $fh);
1063            
1064 0           $DB->commit();
1065 0           return($count);
1066             }
1067              
1068             sub importKeywords($$$$)
1069             {
1070 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
1071 0           my $startTime=time();
1072 0           my $lineCount=0;
1073              
1074 0 0         if ( $DB->table_row_count('Keywords') > 0 ) {
1075 0           $self->status("clearing previously loaded data..");
1076 0           $DB->table_clear('Keywords');
1077             }
1078              
1079 0   0       my $fh = openMaybeGunzip($file) || return(-2);
1080 0           while(<$fh>) {
1081 0           $lineCount++;
1082              
1083 0 0         if ( m/THE KEYWORDS LIST/ ) {
    0          
1084 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
1085 0           $self->error("missing ======= after \"THE KEYWORDS LIST\" at line $lineCount");
1086 0           closeMaybeGunzip($file, $fh);
1087 0           return(-1);
1088             }
1089 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
1090 0           $self->error("missing empty line after ======= at line $lineCount");
1091 0           closeMaybeGunzip($file, $fh);
1092 0           return(-1);
1093             }
1094 0           last;
1095             }
1096             elsif ( $lineCount > 200000 ) {
1097 0           $self->error("$file: stopping at line $lineCount, didn't see \"THE KEYWORDS LIST\" line");
1098 0           closeMaybeGunzip($file, $fh);
1099 0           return(-1);
1100             }
1101             }
1102              
1103 0 0         my $progress=Term::ProgressBar->new({name => "importing Keywords",
1104             count => $countEstimate,
1105             ETA => 'linear'})
1106             if ($self->{showProgressBar});
1107              
1108 0 0         $progress->minor(0) if ($self->{showProgressBar});
1109 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
1110 0           my $next_update=0;
1111              
1112 0           my $count=0;
1113 0           my $countImported=0;
1114 0           my %found;
1115 0           my $tableInsert_sth1=$DB->prepare('INSERT INTO Keywords (KeywordID, Name) VALUES (?,?)');
1116 0           my $tableInsert_sth2=$DB->prepare('INSERT INTO Titles2Keywords (TitleID, KeywordID) VALUES (?,?)');
1117 0           my $keywordCount=0;
1118 0           my $potentialEntries=0;
1119            
1120 0           while(<$fh>) {
1121 0           $lineCount++;
1122 0           my $line=$_;
1123 0           chomp($line);
1124 0 0         next if ($line =~ m/^\s*$/);
1125            
1126 0           $potentialEntries++;
1127            
1128 0           my ($title, $keyword) = ($line =~ m/^(.*)\s+(\S+)\s*$/);
1129 0 0 0       if ( defined($title) and defined($keyword) ) {
1130              
1131 0           my ($episode) = $title =~ m/\s+(\{.*\})$/o;
1132            
1133 0 0         if ( $self->{imdbMovie2DBKey}->{$title} ) {
1134 0 0         if ( !defined($found{$keyword}) ) {
1135 0           $keywordCount++;
1136            
1137 0           $found{$keyword}=$keywordCount;
1138 0           $tableInsert_sth1->execute($keywordCount, $keyword);
1139             #=$DB->insert_row('Keywords', 'KeywordID', Name=>$keyword);
1140             }
1141 0           $tableInsert_sth2->execute($self->{imdbMovie2DBKey}->{$title}, $found{$keyword});
1142            
1143             #$DB->insert_row('Titles2Keywords', undef, TitleID=>$self->{imdbMovie2DBKey}->{$title}, KeywordID=>$found{$keyword});
1144 0           $countImported++;
1145 0 0         if ( ($countImported % 50000) == 0 ) {
1146 0           $DB->commit();
1147             }
1148             }
1149 0           $count++;
1150 0 0         if ( $self->{showProgressBar} ) {
1151             # re-adjust target so progress bar doesn't seem too wonky
1152 0 0         if ( $count > $countEstimate ) {
    0          
1153 0           $countEstimate = $progress->target($count+1000);
1154 0           $next_update=$progress->update($count);
1155             }
1156             elsif ( $count > $next_update ) {
1157 0           $next_update=$progress->update($count);
1158             }
1159             }
1160             } else {
1161 0           $self->error("$file:$lineCount: unrecognized format \"$line\"");
1162             }
1163            
1164             }
1165 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
1166              
1167 0           $self->status(sprintf("importing Keywords found ".withThousands($count)." in ".
1168             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
1169              
1170 0           closeMaybeGunzip($file, $fh);
1171 0           $DB->commit();
1172 0           return($count);
1173             }
1174              
1175             sub importPlots($$$$)
1176             {
1177 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
1178 0           my $startTime=time();
1179 0           my $lineCount=0;
1180              
1181 0 0         if ( $DB->table_row_count('Plots') > 0 ) {
1182 0           $self->status("clearing previously loaded data..");
1183 0           $DB->table_clear('Plots');
1184             }
1185              
1186 0   0       my $fh = openMaybeGunzip($file) || return(-2);
1187 0           while(<$fh>) {
1188 0           $lineCount++;
1189              
1190 0 0         if ( m/PLOT SUMMARIES LIST/ ) {
    0          
1191 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
1192 0           $self->error("missing ======= after \"PLOT SUMMARIES LIST\" at line $lineCount");
1193 0           closeMaybeGunzip($file, $fh);
1194 0           return(-1);
1195             }
1196 0 0 0       if ( !($_=<$fh>) || !m/^-----------/o ) {
1197 0           $self->error("missing ------- line after ======= at line $lineCount");
1198 0           closeMaybeGunzip($file, $fh);
1199 0           return(-1);
1200             }
1201 0           last;
1202             }
1203             elsif ( $lineCount > 500 ) {
1204 0           $self->error("$file: stopping at line $lineCount, didn't see \"PLOT SUMMARIES LIST\" line");
1205 0           closeMaybeGunzip($file, $fh);
1206 0           return(-1);
1207             }
1208             }
1209              
1210 0 0         my $progress=Term::ProgressBar->new({name => "importing Plots",
1211             count => $countEstimate,
1212             ETA => 'linear'})
1213             if ($self->{showProgressBar});
1214              
1215 0 0         $progress->minor(0) if ($self->{showProgressBar});
1216 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
1217 0           my $next_update=0;
1218              
1219 0           my $count=0;
1220 0           my $potentialEntries=0;
1221 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Plots (TitleID, Sequence, Description, Author) VALUES (?,?,?,?)');
1222 0           while(<$fh>) {
1223 0           $lineCount++;
1224 0           my $line=$_;
1225 0           chomp($line);
1226 0 0         next if ($line =~ m/^\s*$/);
1227 0 0         next if ($line=~m/\s*\{\{SUSPENDED\}\}/o);
1228            
1229 0           $potentialEntries++;
1230              
1231 0           my ($title, $episode) = ($line =~ m/^MV:\s(.*?)\s?(\{.*\})?$/);
1232 0 0         if ( defined($title) ) {
1233            
1234 0           $line =~s/^MV:\s*//;
1235              
1236 0           my $sequence=1;
1237 0           my $plot = '';
1238              
1239 0           while ( my $l = <$fh> ) {
1240 0           $lineCount++;
1241 0           chomp($l);
1242            
1243 0 0         next if ($l =~ m/^\s*$/);
1244            
1245 0 0         if ( $l =~ m/PL:\s(.*)$/ ) { # plot summary is a number of lines starting "PL:"
1246 0 0         $plot .= ($plot ne '' ?' ':'') . $1;
1247             }
1248              
1249 0 0 0       if ( $l =~ m/BY:\s(.*)$/ || $l =~ m/^(\-\-\-\-\-\-\-\-)/o ) {
1250 0           my $token=$1;
1251 0           my $author=$1;
1252            
1253 0 0         if ( $token eq "\-\-\-\-\-\-\-\-" ) {
1254 0 0         if ( $plot eq '' ) {
1255 0           last;
1256             }
1257 0           $author='';
1258             }
1259            
1260 0 0         if ( $self->{imdbMovie2DBKey}->{$line} ) {
1261 0           $tableInsert_sth->execute($self->{imdbMovie2DBKey}->{$line}, $sequence, $plot, $author);
1262            
1263 0           $count++;
1264 0 0         if ( ($count % 50000) == 0 ) {
1265 0           $DB->commit();
1266             }
1267             }
1268             else {
1269 0           $self->error("$lineCount: unable to match title key '$line'");
1270             }
1271            
1272 0           $plot='';
1273 0           $sequence++;
1274              
1275 0 0         if ( $token eq "\-\-\-\-\-\-\-\-" ) {
1276 0           last;
1277             }
1278             }
1279             }
1280              
1281 0 0         if ( length($plot) ) {
1282 0           $self->error("$lineCount: truncated plot with title key '$line'");
1283             }
1284            
1285 0 0         if ( $self->{showProgressBar} ) {
1286             # re-adjust target so progress bar doesn't seem too wonky
1287 0 0         if ( $count > $countEstimate ) {
    0          
1288 0           $countEstimate = $progress->target($count+1000);
1289 0           $next_update=$progress->update($count);
1290             }
1291             elsif ( $count > $next_update ) {
1292 0           $next_update=$progress->update($count);
1293             }
1294             }
1295             } else {
1296             # skip lines up to the next "MV:"
1297 0 0         if ($line !~ m/^(---|PL:|BY:)/ ) {
1298 0           $self->error("$file:$lineCount: unrecognized format \"$line\"");
1299             }
1300             #$next_update=$progress->update($count) if ($self->{showProgressBar});
1301 0 0         if ( $count > $next_update ) {
1302 0 0         if ($self->{showProgressBar}) {
1303 0           $next_update=$progress->update($count) ;
1304 0           warn "next $count -> $next_update";
1305             }
1306             }
1307             }
1308             }
1309 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
1310              
1311 0           $self->status(sprintf("importing Plots found ".withThousands($count)." in ".
1312             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
1313              
1314 0           closeMaybeGunzip($file, $fh);
1315 0           $DB->commit();
1316 0           return($count);
1317             }
1318              
1319             sub loadDBInfo($)
1320             {
1321 0     0 0   my $file=shift;
1322 0           my $info;
1323              
1324 0 0         open(INFO, "< $file") || return("imdbDir index file \"$file\":$!");
1325 0           while() {
1326 0           chop();
1327 0 0         if ( s/^([^:]+)://o ) {
1328 0           $info->{$1}=$_;
1329             }
1330             }
1331 0           close(INFO);
1332 0           return($info);
1333             }
1334              
1335             sub dbinfoLoad($)
1336             {
1337 0     0 0   my $self=shift;
1338              
1339 0           my $info=loadDBInfo($self->{moviedbInfo});
1340 0 0         if ( ref $info ne 'HASH' ) {
1341 0           return(1);
1342             }
1343 0           $self->{dbinfo}=$info;
1344 0           return(undef);
1345             }
1346              
1347             sub dbinfoAdd($$$)
1348             {
1349 0     0 0   my ($self, $key, $value)=@_;
1350 0           $self->{dbinfo}->{$key}=$value;
1351             }
1352              
1353             sub dbinfoGet($$$)
1354             {
1355 0     0 0   my ($self, $key, $defaultValue)=@_;
1356 0 0         if ( defined($self->{dbinfo}->{$key}) ) {
1357 0           return($self->{dbinfo}->{$key});
1358             }
1359 0           return($defaultValue);
1360             }
1361              
1362             sub dbinfoSave($)
1363             {
1364 0     0 0   my $self=shift;
1365 0 0         open(INFO, "> $self->{moviedbInfo}") || return(1);
1366 0           for (sort keys %{$self->{dbinfo}}) {
  0            
1367 0           print INFO "".$_.":".$self->{dbinfo}->{$_}."\n";
1368             }
1369 0           close(INFO);
1370 0           return(0);
1371             }
1372              
1373             sub dbinfoGetFileSize($$)
1374             {
1375 0     0 0   my ($self, $key)=@_;
1376            
1377              
1378 0 0         if ( !defined($self->{listFiles}->paths_isset($key) ) ) {
1379 0           die ("invalid call for $key");
1380             }
1381 0           my $filePath=$self->{listFiles}->paths_index($key);
1382 0 0         if ( ! -f $filePath ) {
1383 0           return(0);
1384             }
1385              
1386 0           my $fileSize=int(-s $filePath);
1387              
1388             # if compressed, then attempt to run gzip -l
1389 0 0         if ( $filePath=~m/.gz$/) {
1390 0 0         if ( open(my $fd, "gzip -l $filePath |") ) {
1391             # if parse fails, then defalt to wild ass guess of compression of 65%
1392 0           $fileSize=int(($fileSize*100)/(100-65));
1393              
1394 0           while(<$fd>) {
1395 0 0         if ( m/^\s*\d+\s+(\d+)/ ) {
1396 0           $fileSize=$1;
1397             }
1398             }
1399 0           close($fd);
1400             }
1401             else {
1402             # wild ass guess of compression of 65%
1403 0           $fileSize=int(($fileSize*100)/(100-65));
1404             }
1405             }
1406 0           return($fileSize);
1407             }
1408              
1409             sub _redirect($$)
1410             {
1411 0     0     my ($self, $file)=@_;
1412            
1413 0 0         if ( defined($file) ) {
1414 0 0         if ( !open($self->{logfd}, "> $file") ) {
1415 0           print STDERR "$file:$!\n";
1416 0           return(0);
1417             }
1418 0           $self->{errorCountInLog}=0;
1419             }
1420             else {
1421 0           close($self->{logfd});
1422 0           $self->{logfd}=undef;
1423             }
1424 0           return(1);
1425             }
1426              
1427             =head2 importListComplete
1428              
1429             Check to see if spcified list file has been successfully imported
1430              
1431             =cut
1432              
1433             sub importListComplete($)
1434             {
1435 0     0 1   my ($self, $type)=@_;
1436              
1437 0 0         if ( -f "$self->{imdbDir}/stage-$type.log" ) {
1438 0           return(1);
1439             }
1440 0           return(0);
1441             }
1442              
1443             sub _prepStage
1444             {
1445 0     0     my ($self, $type)=@_;
1446            
1447 0           my $DB=new IMDB::Local::DB(database=>"$self->{imdbDir}/imdb.db");
1448              
1449             # if we're restarting, lets start fresh
1450 0 0         if ( $type eq 'movies' ) {
1451             #warn("recreating db ".$DB->database());
1452 0           $DB->delete();
1453              
1454 0           for my $type ( $self->listTypes() ) {
1455 0           unlink("$self->{imdbDir}/stage-$type.log");
1456             }
1457              
1458             }
1459            
1460 0 0         if ( !$self->_redirect(sprintf("%s/stage-$type.log", $self->{imdbDir})) ) {
1461 0           return(1);
1462             }
1463            
1464 0 0         if ( !$DB->connect() ) {
1465 0           die "imdbdb connect failed:$DBI::errstr";
1466             }
1467              
1468 0           $DB->runSQL("PRAGMA synchronous = OFF");
1469 0           return($DB);
1470              
1471             }
1472              
1473             sub _unprepStage
1474             {
1475 0     0     my ($self, $db)=@_;
1476            
1477 0           $db->commit();
1478 0           $db->disconnect();
1479              
1480 0           $self->_redirect(undef);
1481             }
1482              
1483             sub _importListFile($$$)
1484             {
1485 0     0     my ($self, $DB, $type)=@_;
1486              
1487              
1488 0 0         if ( !grep(/^$type$/, $self->listTypes()) ) {
1489 0           die "invalid type $type";
1490             }
1491            
1492             my $dbinfoCalcEstimate=sub {
1493 0     0     my ($self, $key)=@_;
1494            
1495 0           my %estimateSizePerEntry=(movies=>47,
1496             directors=>258,
1497             actors=>695,
1498             actresses=>779,
1499             genres=>38,
1500             ratings=>68,
1501             keywords=>47,
1502             plot=>731);
1503 0           my $fileSize=$self->dbinfoGetFileSize($key);
1504            
1505 0           my $countEstimate=int($fileSize/$estimateSizePerEntry{$key});
1506            
1507 0           my $filePath=$self->{listFiles}->paths_index($key);
1508            
1509 0           $self->dbinfoAdd($key."_list_file", $filePath);
1510 0           $self->dbinfoAdd($key."_list_file_size", "".int(-s $filePath));
1511 0           $self->dbinfoAdd($key."_list_file_size_uncompressed", $fileSize);
1512 0           $self->dbinfoAdd($key."_list_count_estimate", $countEstimate);
1513 0           return($countEstimate);
1514 0           };
1515              
1516             my $dbinfoCalcBytesPerEntry = sub {
1517 0     0     my ($self, $key, $calcActualForThisNumber)=@_;
1518 0           my $fileSize=$self->dbinfoGetFileSize($key);
1519 0           return(int($fileSize/$calcActualForThisNumber));
1520 0           };
1521              
1522              
1523 0 0         if ( ! -f $self->{listFiles}->paths_index($type) ) {
1524 0           $self->status("no $type file available");
1525 0           return(1);
1526             }
1527              
1528 0 0         if ( $type eq 'movies') {
1529              
1530 0           $DB->drop_table_indexes('Titles');
1531            
1532 0           my $countEstimate=&$dbinfoCalcEstimate($self, "movies");
1533              
1534 0           my $num=$self->importMovies($countEstimate, $self->{listFiles}->paths_index('movies'), $DB);
1535 0 0         if ( $num < 0 ) {
    0          
1536 0 0         if ( $num == -2 ) {
1537 0           $self->error("you need to download ".$self->{listFiles}->paths_index('movies')." from ftp.imdb.com");
1538             }
1539 0           return(1);
1540             }
1541             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1542 0           my $better=&$dbinfoCalcBytesPerEntry($self, "movies", $num);
1543 0           $self->status("ARG estimate of $countEstimate for movies needs updating, found $num ($better bytes/entry)");
1544             }
1545              
1546 0 0         open(OUT, "> $self->{imdbDir}/titles.tsv") || die "$self->{imdbDir}/titles.tsv:$!";
1547 0           for my $mkey (sort keys %{$self->{imdbMovie2DBKey}}) {
  0            
1548 0           print OUT "".$self->{imdbMovie2DBKey}->{$mkey}."\t".$mkey."\n";
1549             }
1550 0           close(OUT);
1551              
1552 0           $self->dbinfoAdd("db_stat_movie_count", "$num");
1553              
1554 0           $self->status("Creating Table indexes..");
1555 0           $DB->create_table_indexes('Titles');
1556              
1557 0           return(0);
1558             }
1559              
1560             # read in keys so we have them for follow-up stages
1561 0 0         if ( !defined($self->{imdbMovie2DBKey}) ) {
1562             #$self->{imdbMovie2DBKey}=$DB->select2Hash("select IMDBKey, TitleID from Titles");
1563            
1564 0           if ( 1 ) {
1565 0 0         open(IN, "< $self->{imdbDir}/titles.tsv") || die "$self->{imdbDir}/titles.tsv:$!";
1566 0           while () {
1567 0           chomp();
1568 0 0         if ( m/^(\d+)\t(.+)/o ) {
1569 0           $self->{imdbMovie2DBKey}->{$2}=$1;
1570             }
1571             }
1572 0           close(IN);
1573             }
1574             }
1575              
1576             # need to read-movie kesy
1577 0 0         if ( $type eq 'directors') {
1578              
1579 0           $DB->drop_table_indexes('Directors');
1580            
1581 0           my $countEstimate=&$dbinfoCalcEstimate($self, "directors");
1582              
1583 0           my $num=$self->importDirectors($countEstimate, $self->{listFiles}->paths_index('directors'), $DB);
1584 0 0         if ( $num < 0 ) {
    0          
1585 0 0         if ( $num == -2 ) {
1586 0           $self->error("you need to download ".$self->{listFiles}->paths_index('directors')." from ftp.imdb.com (see http://www.imdb.com/interfaces)");
1587             }
1588 0           return(1);
1589             }
1590             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1591 0           my $better=&$dbinfoCalcBytesPerEntry($self, "directors", $num);
1592 0           $self->status("ARG estimate of $countEstimate for directors needs updating, found $num ($better bytes/entry)");
1593             }
1594 0           $self->dbinfoAdd("db_stat_director_count", "$num");
1595            
1596 0           $self->status("Creating Table indexes..");
1597 0           $DB->create_table_indexes('Directors');
1598              
1599 0           return(0);
1600             }
1601              
1602 0 0         if ( $type eq 'actors') {
1603 0           $DB->drop_table_indexes('Actors');
1604              
1605             #print "re-reading movies into memory for reverse lookup..\n";
1606 0           my $countEstimate=&$dbinfoCalcEstimate($self, "actors");
1607              
1608             #my $num=$self->readCast("Actors", $countEstimate, "$self->{imdbListFiles}->{actors}");
1609 0           my $num=$self->importActors("Actors", $countEstimate, $self->{listFiles}->paths_index('actors'), $DB);
1610 0 0         if ( $num < 0 ) {
    0          
1611 0 0         if ( $num == -2 ) {
1612 0           $self->error("you need to download ".$self->{listFiles}->paths_index('actors')." from ftp.imdb.com (see http://www.imdb.com/interfaces)");
1613             }
1614 0           return(1);
1615             }
1616             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1617 0           my $better=&$dbinfoCalcBytesPerEntry($self, "actors", $num);
1618 0           $self->status("ARG estimate of $countEstimate for actors needs updating, found $num ($better bytes/entry)");
1619             }
1620 0           $self->dbinfoAdd("db_stat_actor_count", "$num");
1621 0           return(0);
1622             }
1623            
1624 0 0         if ( $type eq 'actresses') {
1625              
1626 0           my $countEstimate=&$dbinfoCalcEstimate($self, "actresses");
1627 0           my $num=$self->importActors("Actresses", $countEstimate, $self->{listFiles}->paths_index('actresses'), $DB);
1628 0 0         if ( $num < 0 ) {
    0          
1629 0 0         if ( $num == -2 ) {
1630 0           $self->error("you need to download ".$self->{listFiles}->paths_index('actresses')." from ftp.imdb.com (see http://www.imdb.com/interfaces)");
1631             }
1632 0           return(1);
1633             }
1634             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1635 0           my $better=&$dbinfoCalcBytesPerEntry($self, "actresses", $num);
1636 0           $self->status("ARG estimate of $countEstimate for actresses needs updating, found $num ($better bytes/entry)");
1637             }
1638 0           $self->dbinfoAdd("db_stat_actress_count", "$num");
1639            
1640 0           $self->status("Creating Table indexes..");
1641 0           $DB->create_table_indexes('Actors');
1642            
1643 0           return(0);
1644             }
1645            
1646 0 0         if ( $type eq 'genres') {
1647 0           $DB->drop_table_indexes('Genres');
1648            
1649 0           my $countEstimate=&$dbinfoCalcEstimate($self, "genres");
1650              
1651 0           my $num=$self->importGenres($countEstimate, $self->{listFiles}->paths_index('genres'), $DB);
1652 0 0         if ( $num < 0 ) {
    0          
1653 0 0         if ( $num == -2 ) {
1654 0           $self->error("you need to download ".$self->{listFiles}->paths_index('genres')." from ftp.imdb.com");
1655             }
1656 0           return(1);
1657             }
1658             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1659 0           my $better=&$dbinfoCalcBytesPerEntry($self, "genres", $num);
1660 0           $self->status("ARG estimate of $countEstimate for genres needs updating, found $num ($better bytes/entry)");
1661             }
1662 0           $self->dbinfoAdd("db_stat_genres_count", "$num");
1663            
1664 0           $self->status("Creating Table indexes..");
1665 0           $DB->create_table_indexes('Genres');
1666              
1667 0           return(0);
1668             }
1669            
1670 0 0         if ( $type eq 'ratings') {
1671 0           $DB->drop_table_indexes('Ratings');
1672            
1673 0           my $countEstimate=&$dbinfoCalcEstimate($self, "ratings");
1674              
1675 0           my $num=$self->importRatings($countEstimate, $self->{listFiles}->paths_index('ratings'), $DB);
1676 0 0         if ( $num < 0 ) {
    0          
1677 0 0         if ( $num == -2 ) {
1678 0           $self->error("you need to download ".$self->{listFiles}->paths_index('ratings')." from ftp.imdb.com");
1679             }
1680 0           return(1);
1681             }
1682             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1683 0           my $better=&$dbinfoCalcBytesPerEntry($self, "ratings", $num);
1684 0           $self->status("ARG estimate of $countEstimate for ratings needs updating, found $num ($better bytes/entry)");
1685             }
1686 0           $self->dbinfoAdd("db_stat_ratings_count", "$num");
1687              
1688 0           $self->status("Creating Table indexes..");
1689 0           $DB->create_table_indexes('Ratings');
1690              
1691 0           return(0);
1692             }
1693            
1694 0 0         if ( $type eq 'keywords') {
1695 0           $DB->drop_table_indexes('Keywords');
1696            
1697 0           my $countEstimate=&$dbinfoCalcEstimate($self, "keywords");
1698             #my $countEstimate=5554178;
1699              
1700 0           my $num=$self->importKeywords($countEstimate, $self->{listFiles}->paths_index('keywords'), $DB);
1701 0 0         if ( $num < 0 ) {
    0          
1702 0 0         if ( $num == -2 ) {
1703 0           $self->error("you need to download ".$self->{listFiles}->paths_index('keywords')." from ftp.imdb.com");
1704             }
1705 0           return(1);
1706             }
1707             elsif ( abs($num - $countEstimate) > $countEstimate*.05 ) {
1708 0           $self->status("ARG estimate of $countEstimate for keywords needs updating, found $num");
1709             }
1710 0           $self->dbinfoAdd("keywords_list_file", $self->{listFiles}->paths_index('keywords'));
1711 0           $self->dbinfoAdd("keywords_list_file_size", -s $self->{listFiles}->paths_index('keywords'));
1712 0           $self->dbinfoAdd("db_stat_keywords_count", "$num");
1713              
1714 0           $self->status("Creating Table indexes..");
1715 0           $DB->create_table_indexes('Keywords');
1716              
1717 0           return(0);
1718             }
1719              
1720 0 0         if ( $type eq 'plot') {
1721 0           $DB->drop_table_indexes('Plots');
1722            
1723 0           my $countEstimate=&$dbinfoCalcEstimate($self, "plot");
1724 0           my $num=$self->importPlots($countEstimate, $self->{listFiles}->paths_index('plot'), $DB);
1725 0 0         if ( $num < 0 ) {
    0          
1726 0 0         if ( $num == -2 ) {
1727 0           $self->error("you need to download ".$self->{listFiles}->paths_index('plot')." from ftp.imdb.com");
1728             }
1729 0           return(1);
1730             }
1731             elsif ( abs($num - $countEstimate) > $countEstimate*.05 ) {
1732 0           $self->status("ARG estimate of $countEstimate for plots needs updating, found $num");
1733             }
1734 0           $self->dbinfoAdd("plots_list_file", $self->{listFiles}->paths_index('plot'));
1735 0           $self->dbinfoAdd("plots_list_file_size", -s $self->{listFiles}->paths_index('plot'));
1736 0           $self->dbinfoAdd("db_stat_plots_count", "$num");
1737            
1738 0           $self->status("Creating Table indexes..");
1739 0           $DB->create_table_indexes('Plots');
1740            
1741 0           return(0);
1742             }
1743              
1744 0           $self->error("invalid type $type");
1745 0           return(1);
1746             }
1747              
1748             =head2 importList
1749              
1750             Import a list file from 'listsDir' into the IMDB::Local Database.
1751             Note: when 'movies' type is specified the database is reset from scratch
1752              
1753             =cut
1754              
1755             sub importList($$)
1756             {
1757 0     0 1   my ($self, $type)=@_;
1758              
1759 0           my $DB=$self->_prepStage($type);
1760              
1761             # lets load our stats
1762 0           $self->dbinfoLoad();
1763              
1764 0           my $startTime=time();
1765 0 0         if ( $self->_importListFile($DB, $type) != 0 ) {
1766 0           $DB->disconnect();
1767 0           return(1);
1768             }
1769              
1770 0           $self->dbinfoAdd("seconds_to_complete_prep_stage_$type", (time()-$startTime));
1771 0           $self->dbinfoSave();
1772              
1773 0           $self->_unprepStage($DB);
1774 0           return(0);
1775             }
1776              
1777             =head2 importAll
1778              
1779             Import all available list files from 'listsDir' into the IMDB::Local Database.
1780             Returns # of list files that produced errors.
1781              
1782             =cut
1783              
1784             sub importAll($$)
1785             {
1786 0     0 1   my ($self, $type)=@_;
1787              
1788 0           my $err=0;
1789 0           for my $type ( $self->listTypes() ) {
1790 0 0         if ( $self->importList($type) != 0 ) {
1791 0           warn("list import $type failed to load, $self->{errorCountInLog} errors in $self->{imdbDir}/stage-$type.log");
1792 0           $err++;
1793             }
1794             }
1795 0           return($err);
1796             }
1797              
1798             =head2 optimize
1799              
1800             Optimize the database for better performance.
1801              
1802             =cut
1803             sub optimize($)
1804             {
1805 0     0 1   my ($self)=@_;
1806              
1807 0           my $DB=new IMDB::Local::DB(database=>"$self->{imdbDir}/imdb.db", db_AutoCommit=>1);
1808            
1809 0 0         if ( !$DB->connect() ) {
1810 0           die "imdbdb connect failed:$DBI::errstr";
1811             }
1812              
1813 0           $DB->runSQL("VACUUM");
1814 0           $DB->disconnect();
1815 0           return(1);
1816             }
1817              
1818             sub _NOT_USED_checkSantity($)
1819             {
1820 0     0     my ($self)=@_;
1821              
1822 0           $self->dbinfoAdd("db_version", $IMDB::Local::VERSION);
1823              
1824 0 0         if ( $self->dbinfoSave() ) {
1825 0           $self->error("$self->{moviedbInfo}:$!");
1826 0           return(1);
1827             }
1828            
1829 0           $self->status("running quick sanity check on database indexes...");
1830 0           my $imdb=new IMDB::Local('imdbDir' => $self->{imdbDir},
1831             'verbose' => $self->{verbose});
1832            
1833 0 0         if ( -e "$self->{moviedbOffline}" ) {
1834 0           unlink("$self->{moviedbOffline}");
1835             }
1836            
1837 0 0         if ( my $errline=$imdb->sanityCheckDatabase() ) {
1838 0 0         open(OFF, "> $self->{moviedbOffline}") || die "$self->{moviedbOffline}:$!";
1839 0           print OFF $errline."\n";
1840 0           print OFF "one of the prep stages' must have produced corrupt data\n";
1841 0           print OFF "report the following details to xmltv-devel\@lists.sf.net\n";
1842            
1843 0           my $info=loadDBInfo($self->{moviedbInfo});
1844 0 0         if ( ref $info eq 'HASH' ) {
1845 0           for my $key (sort keys %{$info}) {
  0            
1846 0           print OFF "\t$key:$info->{$key}\n";
1847             }
1848             }
1849             else {
1850 0           print OFF "\tdbinfo file corrupt\n";
1851 0           print OFF "\t$info";
1852             }
1853 0           print OFF "database taken offline\n";
1854 0           close(OFF);
1855 0 0         open(OFF, "< $self->{moviedbOffline}") || die "$self->{moviedbOffline}:$!";
1856 0           while() {
1857 0           chop();
1858 0           $self->error($_);
1859             }
1860 0           close(OFF);
1861 0           return(1);
1862             }
1863 0           $self->status("sanity intact :)");
1864 0           return(0);
1865             }
1866              
1867             =head1 AUTHOR
1868              
1869             jerryv, C<< >>
1870              
1871             =head1 BUGS
1872              
1873             Please report any bugs or feature requests to C, or through
1874             the web interface at L. I will be notified, and then you'll
1875             automatically be notified of progress on your bug as I make changes.
1876              
1877              
1878              
1879              
1880             =head1 SUPPORT
1881              
1882             You can find documentation for this module with the perldoc command.
1883              
1884             perldoc IMDB::Local
1885              
1886              
1887             You can also look for information at:
1888              
1889             =over 4
1890              
1891             =item * RT: CPAN's request tracker (report bugs here)
1892              
1893             L
1894              
1895             =item * AnnoCPAN: Annotated CPAN documentation
1896              
1897             L
1898              
1899             =item * CPAN Ratings
1900              
1901             L
1902              
1903             =item * Search CPAN
1904              
1905             L
1906              
1907             =back
1908              
1909              
1910             =head1 ACKNOWLEDGEMENTS
1911              
1912              
1913             =head1 LICENSE AND COPYRIGHT
1914              
1915             Copyright 2015 jerryv.
1916              
1917             This program is free software; you can redistribute it and/or modify it
1918             under the terms of the the Artistic License (2.0). You may obtain a
1919             copy of the full license at:
1920              
1921             L
1922              
1923             Any use, modification, and distribution of the Standard or Modified
1924             Versions is governed by this Artistic License. By using, modifying or
1925             distributing the Package, you accept this license. Do not use, modify,
1926             or distribute the Package, if you do not accept this license.
1927              
1928             If your Modified Version has been derived from a Modified Version made
1929             by someone other than you, you are nevertheless required to ensure that
1930             your Modified Version complies with the requirements of this license.
1931              
1932             This license does not grant you the right to use any trademark, service
1933             mark, tradename, or logo of the Copyright Holder.
1934              
1935             This license includes the non-exclusive, worldwide, free-of-charge
1936             patent license to make, have made, use, offer to sell, sell, import and
1937             otherwise transfer the Package with respect to any patent claims
1938             licensable by the Copyright Holder that are necessarily infringed by the
1939             Package. If you institute patent litigation (including a cross-claim or
1940             counterclaim) against any party alleging that the Package constitutes
1941             direct or contributory patent infringement, then this Artistic License
1942             to you shall terminate on the date that such litigation is filed.
1943              
1944             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
1945             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
1946             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
1947             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
1948             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
1949             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
1950             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
1951             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1952              
1953              
1954             =cut
1955              
1956             1; # End of IMDB::Local