root/trunk/devel/FuzzyOcr/Config.pm

Revision 133, 31.9 kB (checked in by decoder, 1 year ago)

Added License tags too all code files

Line 
1 # <@LICENSE>
2 # Licensed to the Apache Software Foundation (ASF) under one or more
3 # contributor license agreements.  See the NOTICE file distributed with
4 # this work for additional information regarding copyright ownership.
5 # The ASF licenses this file to you under the Apache License, Version 2.0
6 # (the "License"); you may not use this file except in compliance with
7 # the License.  You may obtain a copy of the License at:
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 # </@LICENSE>
17
18 use strict;
19 package FuzzyOcr::Config;
20
21 use lib qw(..);
22 use FuzzyOcr::Logging qw(debuglog infolog warnlog errorlog);
23 use FuzzyOcr::Scanset;
24 use FuzzyOcr::Preprocessor;
25 use Mail::SpamAssassin::Logger;
26
27 use Fcntl qw(O_RDWR O_CREAT);
28
29 use base 'Exporter';
30 our @EXPORT_OK = qw/
31     parse_config
32     finish_parsing_end
33     get_config
34     set_config
35     set_pid
36     unset_pid
37     kill_pid
38     set_tmpdir
39     get_tmpdir
40     get_all_tmpdirs
41     get_pms
42     save_pms
43     get_timeout
44     get_scansets
45     get_preprocessor
46     get_thresholds
47     get_wordlist
48     get_mysql_ddb
49     get_db_ref
50     set_db_ref
51     read_words
52     /;
53
54 use constant HAS_DBI => eval { require DBI; };
55 use constant HAS_DBD_MYSQL => eval { require DBD::mysql; };
56 use constant HAS_MLDBM => eval { require MLDBM; require MLDBM::Sync;};
57 use constant HAS_DB_FILE => eval { require DB_File; };
58 use constant HAS_STORABLE => eval { require Storable; };
59
60 #Defines the defaults and reads the configuration and wordlists
61
62 our %Threshold = ();
63 our %words = ();
64 our @scansets;
65 our @preprocessors;
66 our $conf;
67 our $pms;
68 our $timeout;
69 our $pid;
70 our $tmpdir;
71 our @tmpdirs;
72 our $dbref;
73
74 # State of the plugin, already initialized?
75 our $initialized = 0;
76
77 our @bin_utils = qw/gifsicle
78     giffix
79     giftext
80     gifinter
81     giftopnm
82     jpegtopnm
83     pngtopnm
84     bmptopnm
85     tifftopnm
86     ppmhist
87     pamfile
88     ocrad
89     gocr/;
90
91 our @paths = qw(/usr/local/netpbm/bin /usr/local/bin /usr/bin);
92
93 my @img_types = qw/gif png jpeg bmp tiff/;
94
95 sub get_timeout {
96     unless (defined $timeout) {
97         $timeout = Mail::SpamAssassin::Timeout->new({ secs => $conf->{focr_timeout} });
98     }
99     return $timeout;
100 }
101
102 sub set_pid {
103     $pid = shift;
104     debuglog("Saved pid: $pid");
105 }
106
107 sub unset_pid {
108     $pid = 0;
109 }
110
111 sub kill_pid {
112     if ($pid) {
113         infolog("Sending SIGTERM to pid: $pid",2);
114         my $ret = kill POSIX::SIGTERM, $pid;
115         # Wait for zombie process if the process is a zombie (i.e. SIGTERM didn't work)
116         wait();
117         return ($ret, $pid);
118     } else {
119         return (-1, 0);
120     }
121 }
122
123 sub set_tmpdir {
124     $tmpdir = shift;
125     push(@tmpdirs, $tmpdir);
126 }
127
128 sub get_tmpdir {
129     return $tmpdir;
130 }
131
132 sub get_all_tmpdirs {
133     return @tmpdirs;
134 }
135
136 sub save_pms {
137     $pms = shift;
138 }
139
140 sub get_pms {
141     return $pms;
142 }
143
144 sub get_config {
145     return $conf;
146 }
147
148 sub get_wordlist {
149     return \%words;
150 }
151
152 sub get_scansets {
153     if ($conf->{focr_autosort_scanset}) {
154         @scansets = sort { $b->{hit_counter} <=> $a->{hit_counter} } @scansets;
155     }
156     return \@scansets;
157 }
158
159 sub get_preprocessor {
160     my ($label) = @_;
161     foreach (@preprocessors) {
162         if ($_->{label} eq $label) {
163             return $_;
164         }
165     }
166     return 0;
167 }
168
169 sub get_thresholds {
170     return \%Threshold;
171 }
172
173 sub set_db_ref {
174     $dbref = shift;
175 }
176
177 sub get_db_ref {
178     return $dbref;
179 }
180
181 sub get_mysql_ddb {
182     return undef unless (HAS_DBI and HAS_DBD_MYSQL);
183
184     my $conf = get_config();
185     my %dopts = ( AutoCommit => 1 );
186     my $dsn = "dbi:mysql:database=".$conf->{focr_mysql_db};
187     if (defined($conf->{focr_mysql_socket})) {
188         $dsn .= ";mysql_socket=".$conf->{focr_mysql_socket};
189     } else {
190         $dsn .= ";host=".$conf->{focr_mysql_host};
191     $dsn .= ";port=".$conf->{focr_mysql_port} if $conf->{focr_mysql_port} != 3306;
192     }
193     debuglog("Connecting to: $dsn");
194     my $ddb = DBI->connect($dsn,
195         $conf->{focr_mysql_user},
196         $conf->{focr_mysql_pass},
197         \%dopts);
198     return $ddb;
199 }
200
201 sub set_config {
202     my($self, $conf) = @_;
203     my @cmds = ();
204
205     foreach my $t (qw/s h w cn/) {
206         push (@cmds, {
207             setting => 'focr_threshold_'.$t,
208             default => 0.01,
209             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
210         });
211     }
212     foreach my $t (qw/c max_hash/) {
213         push (@cmds, {
214             setting => 'focr_threshold_'.$t,
215             default => 5,
216             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
217         });
218     }
219     foreach my $t (qw/height width/) {
220         push (@cmds, {
221             setting => 'focr_min_'.$t,
222             default => 4,
223             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
224         });
225         push (@cmds, {
226             setting => 'focr_max_'.$t,
227             default => 800,
228             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
229         });
230     }
231     push (@cmds, {
232         setting => 'focr_threshold',
233         default => 0.25,
234         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
235     });
236
237     push (@cmds, {
238         setting => 'focr_counts_required',
239         default => 2,
240         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
241     });
242
243     push (@cmds, {
244         setting => 'focr_verbose',
245         default => 1,
246         code => sub {
247             my ($self, $key, $value, $line) = @_;
248             unless (defined $value && $value !~ m/^$/) {
249                 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
250             }
251             unless ($value =~ m/^[0-9]+$/) {
252                 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
253             }
254             $self->{focr_verbose} = $value+0;
255         }
256     });
257
258     push (@cmds, {
259         setting => 'focr_timeout',
260         default => 10,
261         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
262     });
263
264     push (@cmds, {
265         setting => 'focr_global_timeout',
266         default => 0,
267         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
268     });
269
270     push (@cmds, {
271         setting => 'focr_logfile',
272         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
273     });
274
275     push (@cmds, {
276         setting => 'focr_log_stderr',
277         default => 1,
278         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
279     });
280
281     push (@cmds, {
282         setting => 'focr_log_pmsinfo',
283         default => 1,
284         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
285     });
286
287     push (@cmds, {
288         setting => 'focr_enable_image_hashing',
289         default => 0,
290         code => sub {
291             my ($self, $key, $value, $line) = @_;
292             unless (defined $value && $value !~ m/^$/) {
293                 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
294             }
295             unless ($value =~ m/^[0123]$/) {
296                 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
297             }
298             $self->{focr_enable_image_hashing} = $value+0;
299         }
300     });
301
302     push (@cmds, {
303         setting => 'focr_hashing_learn_scanned',
304         default => 1,
305         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
306     });
307
308     push (@cmds, {
309         setting => 'focr_skip_updates',
310         default => 0,
311         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
312     });
313
314     push (@cmds, {
315         setting => 'focr_digest_db',
316         default => "/etc/mail/spamassassin/FuzzyOcr.hashdb",
317         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
318     });
319
320     push (@cmds, {
321         setting => 'focr_global_wordlist',
322         default => "/etc/mail/spamassassin/FuzzyOcr.words",
323         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
324     });
325
326     push (@cmds, {
327         setting => 'focr_personal_wordlist',
328         default => "__userstate__/FuzzyOcr.words",
329         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
330     });
331     push (@cmds, {
332         setting => 'focr_no_homedirs',
333         default => 0,
334         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
335     });
336     push (@cmds, {
337         setting => 'focr_db_hash',
338         default => "/etc/mail/spamassassin/FuzzyOcr.db",
339         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
340     });
341
342     push (@cmds, {
343         setting => 'focr_db_safe',
344         default => "/etc/mail/spamassassin/FuzzyOcr.safe.db",
345         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
346     });
347
348     push (@cmds, {
349         setting => 'focr_db_max_days',
350         default => 35,
351         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
352     });
353
354     push (@cmds, {
355         setting => 'focr_keep_bad_images',
356         default => 0,
357         code => sub {
358             my ($self, $key, $value, $line) = @_;
359             unless (defined $value && $value !~ m/^$/) {
360                 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
361             }
362             unless ($value =~ m/^[012]$/) {
363                 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
364             }
365             $self->{focr_keep_bad_images} = $value+0;
366         }
367     });
368
369     push (@cmds, {
370         setting => 'focr_strip_numbers',
371         default => 1,
372         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
373     });
374
375     push (@cmds, {
376         setting => 'focr_twopass_scoring_factor',
377         default => 1.5,
378         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
379     });
380     push (@cmds, {
381         setting => 'focr_unique_matches',
382         default => 0,
383         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
384     });
385
386     push (@cmds, {
387         setting => 'focr_score_ham',
388         default => 0,
389         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
390     });
391
392     push (@cmds, {
393         setting => 'focr_base_score',
394         default => 5,
395         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
396     });
397
398     push (@cmds, {
399         setting => 'focr_add_score',
400         default => 1,
401         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
402     });
403
404     push (@cmds, {
405         setting => 'focr_corrupt_score',
406         default => 2.5,
407         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
408     });
409
410     push (@cmds, {
411         setting => 'focr_corrupt_unfixable_score',
412         default => 5,
413         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
414     });
415
416     push (@cmds, {
417         setting => 'focr_wrongctype_score',
418         default => 1.5,
419         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
420     });
421
422     push (@cmds, {
423         setting => 'focr_wrongext_score',
424         default => 1.5,
425         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
426     });
427
428     push (@cmds, {
429         setting => 'focr_autodisable_score',
430         default => 10,
431         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
432     });
433
434     push (@cmds, {
435         setting => 'focr_autodisable_negative_score',
436         default => -5,
437         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
438     });
439
440     push (@cmds, {
441         setting => 'focr_path_bin',
442         default => '/usr/local/netpbm/bin:/usr/local/bin:/usr/bin',
443         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
444     });
445
446     foreach (@bin_utils) {
447         push (@cmds, {
448             setting => 'focr_bin_'.$_,
449             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
450         });
451     }
452
453     foreach (@img_types) {
454         push (@cmds, {
455             setting => 'focr_skip_'.$_,
456             default => 0,
457             type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
458         });
459         push (@cmds, {
460             setting => 'focr_max_size_'.$_,
461             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
462         });
463     }
464
465     push (@cmds, {
466         setting => 'focr_scan_pdfs',
467         default => 0,
468         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
469     });
470     push (@cmds, {
471         setting => 'focr_pdf_maxpages',
472         default => 1,
473         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
474     });
475
476     push (@cmds, {
477         setting => 'focr_scanset_file',
478         default => '/etc/mail/spamassassin/FuzzyOcr.scansets',
479         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
480     });
481     push (@cmds, {
482         setting => 'focr_preprocessor_file',
483         default => '/etc/mail/spamassassin/FuzzyOcr.preps',
484         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
485     });
486
487     push (@cmds, {
488         setting => 'focr_minimal_scanset',
489         default => 1,
490         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
491     });
492     push (@cmds, {
493         setting => 'focr_autosort_scanset',
494         default => 1,
495         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
496     });
497     push (@cmds, {
498         setting => 'focr_autosort_buffer',
499         default => 10,
500         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
501     });
502     push (@cmds, {
503         setting => 'focr_mysql_host',
504         default => 'localhost',
505         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
506     });
507
508     push (@cmds, {
509         setting => 'focr_mysql_port',
510         default => 3306,
511         type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
512     });
513     push (@cmds, {
514         setting => 'focr_mysql_socket',
515         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
516     });
517     push (@cmds, {
518         setting => 'focr_mysql_db',
519         default => 'FuzzyOcr',
520         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
521     });
522     push (@cmds, {
523         setting => 'focr_mysql_hash',
524         default => 'Hash',
525         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
526     });
527     push (@cmds, {
528         setting => 'focr_mysql_safe',
529         default => 'Safe',
530         type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
531     });
532     push (@cmds, {
533         setting => 'focr_mysql_update_hash',
534         default => 0,
535         type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
536     });
537     foreach (qw/user pass/) {
538         push (@cmds, {
539            setting => 'focr_mysql_'.$_,
540             default => 'fuzzyocr',
541             type =>  $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
542         });
543     }
544
545     $conf->{parser}->register_commands(\@cmds);
546 }
547
548 sub parse_config {
549     my ($self, $opts) = @_;
550
551     # Don't parse a config twice
552     if ($initialized) { return 1; }
553
554     if ($opts->{key} eq 'focr_end_config') {
555         $conf = $opts->{conf};
556         my $main = $self->{main};
557         my $retcode;
558
559         # Parse preprocessor file
560         my $pfile = $conf->{'focr_preprocessor_file'};
561         infolog("Starting preprocessor parser for file \"$pfile\"...");
562         ($retcode, @preprocessors) = parse_preprocessors($pfile);
563         if ($retcode) {
564             errorlog("Error parsing preprocessor file \"$pfile\", aborting...");
565             return 0;
566         }
567
568         # Parse scanset file
569         my $sfile = $conf->{'focr_scanset_file'};
570         infolog("Starting scanset parser for file \"$sfile\"...");
571         ($retcode, @scansets) = parse_scansets($sfile);
572         if ($retcode) {
573             errorlog("Error parsing scanset file \"$sfile\", aborting...");
574             return 0;
575         }
576
577         return 1;
578     } elsif ($opts->{key} eq 'focr_bin_helper') {
579         my @cmd; $conf = $opts->{conf};
580         my $val = $opts->{value}; $val =~ s/[\s]*//g;
581         debuglog("focr_bin_helper: '$val'");
582         foreach my $bin (split(',',$val)) {
583             unless (grep {m/$bin/} @bin_utils) {
584                 push @bin_utils, $bin;
585                 push (@cmd, {
586                     setting => 'focr_bin_'.$bin,
587                     type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
588                 });
589             } else {
590                 warnlog("$bin is already defined, skipping...");
591             }
592         }
593         if (scalar(@cmd)>0) {
594             infolog("Adding <".scalar(@cmd)."> new helper apps");
595             $conf->{parser}->register_commands(\@cmd)
596         }
597         return 1;
598     }
599     return 0;
600 }
601
602 sub finish_parsing_end {
603     my ($self, $opts) = @_;
604
605     # Don't call this function twice
606     if ($initialized) { return 1; }
607
608     my $main = $self->{main};
609     $conf = $opts->{conf};
610
611     # find external binaries
612     @paths = split(/:/, $conf->{focr_path_bin});
613     infolog("Searching in: $_") foreach @paths;
614     foreach my $a (@bin_utils) {
615         my $b = "focr_bin_$a";
616         if (defined $conf->{$b} and ! -x $conf->{$b}) {
617             infolog("cannot exec $a, removing...");
618             delete $conf->{$b};
619         }
620         if (defined $conf->{$b}) {
621             debuglog("Using $a => $conf->{$b}");
622         } else {
623             foreach my $p (@paths) {
624                 my $f = "$p/$a";
625                 next unless -x $f;
626                 $conf->{$b} = $f;
627                 last;
628             }
629             if (defined $conf->{$b}) {
630                 infolog("Using $a => $conf->{$b}");
631             } else {
632                 warnlog("Cannot find executable for $a");
633             }
634         }
635     }
636
637     # Allow scanning if in debug mode?
638     $conf->{focr_autodisable_score} = 1000
639         if $Mail::SpamAssassin::Logger::LOG_SA{level} == 3;
640
641     # Extract Thresholds
642     foreach my $k (keys %{$conf}) {
643         if ($k =~ m/^focr_threshold_(\S+)/) {
644             $Threshold{$1} = $conf->{$k};
645             debuglog("Threshold[$1] => $conf->{$k}");
646         }
647     }
648     # Display All Options
649     foreach my $k (sort keys %{$conf}) {
650         next unless $k =~ m/^focr_/;
651         next if $k =~ m/^focr_bin_/;
652         next if $k =~ m/^focr_mysql_pass/;
653         next if $k =~ m/^focr_threshold_/;
654         debuglog(" $k => ".$conf->{$k});
655     }
656
657     unless (@scansets) {
658         warn("No scansets loaded, did you remove the \"focr_config_end\" line at the end of the .cf file?");
659     }
660
661     foreach my $prep (@preprocessors) {
662         my $preplabel = $prep->{label};
663         my $off = ($prep->{command} =~ m/^\$/) ? 1 : 0;
664         my $t = 'focr_bin_'.substr($prep->{command},$off);
665         #Replace command with full path if known
666         $prep->{command} = $conf->{$t} if defined $conf->{$t};
667         my $prepcmd = $prep->{command};
668         if (defined $prep->{args}) {
669             $prepcmd .= ' ' . $prep->{args};
670         }
671         infolog("Loaded preprocessor $preplabel: $prepcmd");
672     }
673
674     foreach my $scan (@scansets) {
675         my $scanlabel = $scan->{label};
676         my $off = ($scan->{command} =~ m/^\$/) ? 1 : 0;
677         my $t = 'focr_bin_'.substr($scan->{command},$off);
678         #Replace command with full path if known
679         $scan->{command} = $conf->{$t} if defined $conf->{$t};
680         my $scancmd = $scan->{command};
681         if (defined $scan->{args}) {
682             $scancmd .= ' ' . $scan->{args};
683         }
684         infolog("Using scan $scanlabel: $scancmd");
685     }
686
687     if ($conf->{focr_enable_image_hashing} == 3) {
688         unless (HAS_DBI and HAS_DBD_MYSQL) {
689             $conf->{focr_enable_image_hashing} = 0;
690             errorlog("Disable Image Hashing");
691             errorlog("Missing DBI") unless HAS_DBI;
692             errorlog("Missing DBD::mysql") unless HAS_DBD_MYSQL;
693         }
694
695         # Warn if MLDBM databases are present, but can't be imported
696         unless (HAS_MLDBM and HAS_DB_FILE and HAS_STORABLE and (-r $conf->{focr_db_hash} or -r $conf->{focr_db_safe})) {
697             infolog("Importing for MLDBM databases not available (dependencies missing)");
698         }
699     }
700     if ($conf->{focr_enable_image_hashing} == 2) {
701         unless (HAS_MLDBM and HAS_DB_FILE and HAS_STORABLE) {
702             $conf->{focr_enable_image_hashing} = 0;
703             errorlog("Disable Image Hashing");
704             errorlog("Missing MLDBM and/or MLDBM::Sync") unless HAS_MLDBM;
705             errorlog("Missing DB_File") unless HAS_DB_FILE;
706             errorlog("Missing Storable") unless HAS_STORABLE;
707         }
708     }
709     unless ($conf->{focr_skip_updates}) {
710         if ($conf->{focr_enable_image_hashing} == 2 and -r $conf->{focr_digest_db}) {
711             import MLDBM qw(DB_File Storable);
712             my %DB; my $dbm; my $sdbm; my $err = 0;
713             my $now = time - ($conf->{focr_db_max_days}*86400);
714             $sdbm = tie %DB, 'MLDBM::Sync', $conf->{focr_db_hash} or $err++;
715             if ($err) {
716                 errorlog("Could not open \"$conf->{focr_db_hash}\"");
717             } else {
718                 $sdbm->Lock;
719                 my $hash = 0;
720                 infolog("Expiring records prior to: ".scalar(localtime($now)));
721                 foreach my $k (keys %DB) {
722                     my $db = $DB{$k};
723                     if ($db->{check} < $now) {
724                         infolog("Expire: <$k> Reason: $db->{check} < $now");
725                         delete $DB{$k}; $hash++;
726                     }
727                 }
728                 infolog("Expired <$hash> Image Hashes after $conf->{focr_db_max_days} day(s)")
729                     if ($hash>0);
730                 $hash = 0;
731                 open HASH, $conf->{focr_digest_db};
732                 while (<HASH>) {
733                     chomp;
734                     my($score,$basic,$key) = split('::',$_,3);
735                     next if (defined $DB{$key});
736                     $dbm = $DB{$key};
737                     $dbm->{score} = $score;
738                     $dbm->{basic} = $basic;
739                     $dbm->{input} =
740                     $dbm->{check} = time;
741                     $dbm->{match} = 1;
742                     $DB{$key} = $dbm;
743                     $hash++;
744                 }
745                 close HASH;
746                 infolog("Imported <$hash> Image Hashes from \"$conf->{focr_digest_db}\"")
747                     if ($hash>0);
748                 $hash = scalar(keys %DB);
749                 infolog("<$hash> Known BAD Image Hashes Available");
750                 $sdbm->UnLock;
751                 undef $sdbm;
752                 untie %DB;
753             }
754             $err = 0;
755             $sdbm = tie %DB, 'MLDBM::Sync', $conf->{focr_db_safe} or $err++;
756             if ($err) {
757                 errorlog("Could not open \"$conf->{focr_db_safe}\"");
758             } else {
759                 $sdbm->Lock;
760                 my $hash = 0;
761                 foreach my $k (keys %DB) {
762                     my $db = $DB{$k};
763                     if ($db->{check} < $now) {
764                         infolog("Expire: <$k> Reason: $db->{check} < $now");
765                         delete $DB{$k}; $hash++;
766                     }
767                 }
768                 infolog("Expired <$hash> Image Hashes after $conf->{focr_db_max_days} day(s)")
769                     if ($hash>0);
770                 $hash = scalar(keys %DB);
771                 infolog("<$hash> Known GOOD Image Hashes Available");
772                 $sdbm->UnLock;
773                 undef $sdbm;
774                 untie %DB;
775             }
776         }
777         if ($conf->{focr_enable_image_hashing} == 3 and defined (my $ddb = get_mysql_ddb())
778             and (-r $conf->{focr_db_hash} or -r $conf->{focr_db_safe})
779             and HAS_MLDBM and HAS_DB_FILE and HAS_STORABLE) {
780
781             import MLDBM qw(DB_File Storable);
782             my $db   = $conf->{focr_mysql_db};
783             my $tab  = $conf->{focr_mysql_hash};
784             my $file = $conf->{focr_db_hash};
785             my %DB; my $dbm; my $sdbm; my $err = 0;
786             $sdbm = tie %DB, 'MLDBM::Sync', $file or $err++;
787             if ($err) {
788                 errorlog("Could not open \"$file\"");
789             } else {
790                 $sdbm->ReadLock;
791                 foreach my $k (keys %DB) {
792                     my $dbm = $DB{$k};
793                     my $sql = qq(select score from $db.$tab where $tab.key='$k');
794                     my @data = $ddb->selectrow_array($sql);
795                     unless (scalar(@data)>0) {
796                         $sql  = "insert into $db.$tab values ('$k'";
797                         foreach my $y (qw/basic fname ctype/) {
798                             my $val = defined($dbm->{$y}) ? $dbm->{$y} : '';
799                             $sql .= ",'$val'";
800                         }
801                            if ($dbm->{ctype} =~ m/gif/i)      { $sql .= ",'1'"; }
802                         elsif ($dbm->{ctype} =~ m/jpg|jpeg/i) { $sql .= ",'2'"; }
803                         elsif ($dbm->{ctype} =~ m/png/i)      { $sql .= ",'3'"; }
804                         elsif ($dbm->{ctype} =~ m/bmp/i)      { $sql .= ",'4'"; }
805                         elsif ($dbm->{ctype} =~ m/tiff/i)     { $sql .= ",'5'"; }
806                         else                                  { $sql .= ",'0'"; }
807                         foreach my $y (qw/match input check score dinfo/) {
808                             my $val = defined($dbm->{$y}) ? $dbm->{$y} : '';
809                             $sql .= ",'$val'";
810                         }
811                         $sql .= ")";
812                         debuglog($sql);
813                         $ddb->do($sql); $err++;
814                     }
815                 }
816                 $sdbm->UnLock;
817                 undef $sdbm;
818                 untie %DB;
819                 infolog("Stored [$err] Hashes in $db.$tab") if $err>0;
820             }
821             $tab  = $conf->{focr_mysql_safe};
822             $file = $conf->{focr_db_safe};
823             $err  = 0;
824             $sdbm = tie %DB, 'MLDBM::Sync', $file or $err++;
825             if ($err) {
826                 errorlog("Could not open \"$file\"");
827             } else {
828                 $sdbm->ReadLock;
829                 foreach my $k (keys %DB) {
830                     my $dbm = $DB{$k};
831                     my $sql = qq(select score from $db.$tab where $tab.key='$k');
832                     my @data = $ddb->selectrow_array($sql);
833                     unless (scalar(@data)>0) {
834                         $sql  = "insert into $db.$tab values ('$k'";
835                         foreach my $y (qw/basic fname ctype/) {
836                             my $val = defined($dbm->{$y}) ? $dbm->{$y} : '';
837                             $sql .= ",'$val'";
838                         }
839                            if ($dbm->{ctype} =~ m/gif/i)      { $sql .= ",'1'"; }
840                         elsif ($dbm->{ctype} =~ m/jpg|jpeg/i) { $sql .= ",'2'"; }
841                         elsif ($dbm->{ctype} =~ m/png/i)      { $sql .=