#!/usr/bin/perl
#
# Copyright (c) 2016-2024, Bill MacAllister <bill@ca-zephyr.org>
# File: ring-load-daemon
# Description: load pictures into a ring database

use strict;
use DBI;
use File::Compare;
use File::Copy;
use File::Slurp;
use File::Spec;
use Getopt::Long;
use Image::ExifTool 'ImageInfo';
use Image::Magick;
use Pod::Usage;
use Rings::Common;

my %MIME_TYPES = ();
my %SIZE_IDS   = ();

my $opt_debug;
my $opt_force;
my $opt_help;
my $opt_id;
my $opt_manual;
my $opt_oneshot;

##############################################################################
# Subroutines
##############################################################################

# ------------------------------------------------
# return regex target string with all files types supported

sub get_file_types {
    my %file_types = ();
    for my $t (keys %MIME_TYPES) {
        $file_types{ $MIME_TYPES{$t} }++;
    }
    my $type_list = '';
    my $or        = '(';
    for my $t (keys %file_types) {
        $type_list .= $or . $t;
        $or = '|';
    }
    if ($or eq '|') {
        $type_list .= ')';
    }
    return $type_list;
}

# ------------------------------------------------
# process the files

sub save_file {

    (my $a_fullname) = @_;

    if (!-e $a_fullname) {
        return "File not found $a_fullname";
    }

    if ($CONF->debug) {
        dbg("Saving file: $a_fullname");
    }

    # Pull out the file name, the file type, and the parent directory
    my ($a_vol, $a_dirs, $a_filename) = File::Spec->splitpath($a_fullname);
    my ($a_mime_type, $a_filetype) = validate_mime_type($a_fullname);
    my $a_lot
      = normalize_lot(substr($a_dirs, length($CONF->picture_input_root)));

    my $in_file_size = -s $a_fullname;
    if ($CONF->debug) {
        dbg("file:$a_fullname size:$in_file_size");
    }
    if ($in_file_size < 1) {
        return "Zero length file $a_fullname";
    }

    # Get meta data
    my %pic = get_meta_data($a_fullname);
    dbg("Done reading file and meta data from: $a_fullname");

    # Check for duplicate picture
    if (!$opt_force) {
        my $dup_cnt = 0;
        # Check the signature for a duplication
        my $sql = "SELECT pid FROM pictures_information ";
        $sql .= "WHERE raw_signature = ? ";
        if ($CONF->debug) {
            dbg($sql);
        }
        my $sth = $DBH->prepare($sql);
        $sth->execute($pic{ring_signature});
        if ($sth->err) {
            sql_die($sql, $sth->err, $sth->errstr);
        }
        while (my $row = $sth->fetchrow_hashref()) {
            $dup_cnt++;
            msg(
                'info',
                "File signature for $a_fullname duplicates $row->{pid}"
            );
        }
        # But duplicate pictures can have different signature, so
        # check other data as well.
        if (!$dup_cnt) {
            my $this_file = $a_filename;
            $this_file =~ s/[.]*$a_filetype$//xms;
            my $sel = 'SELECT pid, raw_picture_size ';
            $sel .= 'FROM pictures_information ';
            $sel .= 'WHERE file_name LIKE ? ';
            $sel .= 'AND camera = ? ';
            $sel .= 'AND shutter_speed = ? ';
            $sel .= 'AND fstop = ? ';
            $sel .= "ORDER BY pid ";
            my $sth = $DBH->prepare($sel);
            $sth->execute(
                '%' . $this_file . '%',
                $pic{'ring_camera'},
                $pic{'ring_shutterspeed'},
                $pic{'ring_fstop'}
            );
            if ($sth->err) {
                sql_die($sel, $sth->err, $sth->errstr);
            }

            while (my $row = $sth->fetchrow_hashref()) {
                if ($row->{raw_picture_size} > 0) {
                    my $size_match = $in_file_size / $row->{raw_picture_size};
                    if ($size_match < 1.05 && $size_match > .95) {
                        $dup_cnt++;
                        msg(
                            'info',
                            "File size of $a_fullname duplicates $row->{pid}"
                        );
                    }
                }
            }
        }
        if ($dup_cnt > 0) {
            msg('info', "SKIPPING: $a_fullname");
            return;
        }
    }

    # Set the PID
    my $pid = get_next_id("pid");

    # Set the path for the new image
    my $pic_path = create_picture_dirs($a_lot, 'raw');
    my $pic_base = lc("${pid}.${a_filetype}");
    my $pic_file = "${pic_path}/${pic_base}";

    # Copy the file into place
    if (!copy($a_fullname, $pic_file)) {
        my $m = "Problem copying $a_fullname to $pic_file, $!";
        msg('warn', $m);
        return $m;
    }

    # Get the metadata of the new file and store it
    my %new_pic = get_meta_data($pic_file);
    store_meta_data($a_lot, $pid, \%new_pic);
    create_picture($pid, 'raw', $pic_file, \%new_pic);

    queue_action_set($pid, 'SIZE');
    set_new_picture($pid);

    return;
}

# ------------------------------------------------
# Set a list of directories to process from the picture_action_queue

sub get_directories {
    my @dir_list = ();
    my $sql = "SELECT * from picture_upload_queue WHERE status = 'PENDING'";
    if ($CONF->debug) {
        dbg($sql);
    }
    my $sth = $DBH->prepare($sql);
    $sth->execute();
    if ($sth->err) {
        sql_die($sql, $sth->err, $sth->errstr);
    }
    while (my $row = $sth->fetchrow_hashref()) {
        push @dir_list, $row->{path};
    }
    return @dir_list;
}

# ------------------------------------------------
# process directories that contain pictures

sub process_directories {
    my @dir_list  = @_;
    my $type_list = get_file_types();

    for my $this_dir (@dir_list) {
        my %file_list = ();
        if (!-d $this_dir) {
            my $m = "$this_dir not found";
            msg('info', "$m ... skipping");
            queue_upload_error($this_dir, $m);
            next;
        }
        $this_dir =~ s{/$}{}xms;
        msg(
            'info',
            "Examining files that match *.${type_list} in ${this_dir}"
        );
        opendir(my $dh, $this_dir) || die "ERROR: problem opening $this_dir\n";
        while (readdir $dh) {
            my $f = $_;
            if ($f =~ /(.*?)\.$type_list$/xmsi) {
                my $full_path = "$this_dir/$f";
                $file_list{$full_path}++;
            }
        }
        for my $f (sort keys %file_list) {
            my $m = save_file($f);
            if ($m) {
                queue_upload_error($this_dir, $m);
                next;
            }
        }
        queue_upload_reset($this_dir);
    }
    return;
}

##############################################################################
# Main Routine
##############################################################################

# -- get options
GetOptions(
    'debug'   => \$opt_debug,
    'force'   => \$opt_force,
    'help'    => \$opt_help,
    'id=s'    => \$opt_id,
    'manual'  => \$opt_manual,
    'oneshot' => \$opt_oneshot
);

# help the poor souls out
if (@ARGV && $ARGV[0] eq 'help') {
    $opt_help = 1;
}
if ($opt_help) {
    pod2usage(-verbose => 0);
}
if ($opt_manual) {
    pod2usage(-verbose => 2);
}

my %id_list = ();
if ($opt_id) {
    $opt_oneshot = 1;
    $id_list{$opt_id} = 1;
} else {
    %id_list = get_id_list();
}

if ($opt_debug) {
    print("DEBUG: Debugging started.");
}

my $end = 0;
while ($end == 0) {
    if ($opt_oneshot) {
        $end = 1;
    }
    my $rest        = 60;
    my $update_flag = 0;
    for my $id (keys %id_list) {
        my $ring_conf = "/etc/rings/${id}.conf";
        get_config($ring_conf);
        if ($opt_debug) {
            $CONF->debug(1);
        }
        if ($CONF->debug) {
            dbg("ring_conf = $ring_conf\n");
        }
        db_connect();
        my @dir_list;
        if (@ARGV) {
            @dir_list    = @ARGV;
            $opt_oneshot = 1;
        } else {
            @dir_list = get_directories();
        }
        if (scalar(@dir_list) > 0) {
            %MIME_TYPES = get_picture_types();
            %SIZE_IDS   = get_picture_sizes();
            process_directories(@dir_list);
            $update_flag = 1;
        }
        if ($CONF->queue_sleep > $rest) {
            $rest = $CONF->queue_sleep;
        }
        db_disconnect();
    }
    if ($opt_oneshot) {
        $end = 1;
    } elsif (!$update_flag) {
        sleep $rest;
    }
}

exit;

__END__

=head1 NAME

ring-load-daemon - load pictures into the rings

=head1 SYNOPSIS

ring-load-daemon <directory1> [<directory2> ...] [--conf=<configuration>]
[--oneshot] [--force] [--debug] [--help] [--manual]


=head1 DESCRIPTION

This script reads the jpeg files in a directory and loads the rings
database.  The default action is loop reading 'UPLOAD' entries from
the picture_action_queue and process them.  If a directories are
specified on the command line then the directories are process and the
script is exited.

=head1 OPTIONS AND ARGUMENTS

=over 4

=item --force

Override duplicate detection and store the pictures anyway.

=item --onetime

Process the current file list, either from the command line or the
picture_action_queue, and exit.

=item --help

Displays help text.

=item --manual

Displays more complete help text.

=item --debug

Turns on debugging displays.

=back

=head1 AUTHOR

Bill MacAllister <bill@ca-zephyr.org>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2016-2024, Bill MacAllister <bill@ca-zephyr.org>.

This code is free software; you can redistribute it and/or modify it
under the same terms as Perl. For more details, see the full
text of the at https://opensource.org/licenses/Artistic-2.0.

This program is distributed in the hope that it will be
useful, but without any warranty; without even the implied
warranty of merchantability or fitness for a particular purpose.

=cut
