#!/usr/bin/perl
#-----------------------------------------------------------------------------
#
#  Tirex Tile Rendering System
#
#  tirex-syncd
#
#-----------------------------------------------------------------------------
#  See end of this file for documentation.
#-----------------------------------------------------------------------------
#
#  Copyright (C) 2010  Frederik Ramm <frederik.ramm@geofabrik.de> and
#                      Jochen Topf <jochen.topf@geofabrik.de>
#  
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#  
#  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.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; If not, see <http://www.gnu.org/licenses/>.
#
#-----------------------------------------------------------------------------

use strict;
use warnings;

use Getopt::Long qw( :config gnu_getopt );
use IO::Socket;
use POSIX 'setsid';
use Pod::Usage qw();
use Socket;

use Tirex;
use Tirex::Map;

#-----------------------------------------------------------------------------

my %opts = ();
GetOptions( \%opts, 'help|h', 'debug|d', 'config|c=s', 'dry-run|n' ) or exit(2);

if ($opts{'help'})
{
    Pod::Usage::pod2usage(
        -verbose => 1,
        -msg     => "tirex-syncd - sync rendered tiles to backup host\n",
        -exitval => 0
    );
}

$Tirex::DEBUG = 1 if ($opts{'debug'});

my $dryrun = $opts{'dry-run'};

my $config_dir = $opts{'config'} || $Tirex::TIREX_CONFIGDIR;
my $config_file = $config_dir . '/' . $Tirex::TIREX_CONFIGFILENAME;
Tirex::Config::init($config_file);
Tirex::Renderer->read_config_dir($config_dir);

my $syncd_udp_port = Tirex::Config::get('syncd_udp_port', $Tirex::SYNCD_UDP_PORT, qr{^[1-9][0-9]{1,4}$});
my $delay = Tirex::Config::get('syncd_aggregate_delay', $Tirex::SYNCD_AGGREGATE_DELAY, qr{^\d+$});
my $syncd_command = Tirex::Config::get('syncd_command', $Tirex::SYNCD_COMMAND);

my $socket = IO::Socket::INET->new( LocalAddr => 'localhost', LocalPort => $syncd_udp_port, Proto => 'udp', ReuseAddr => 1, Blocking => 0 ) or die("Can't open UDP socket: :$!\n");

my $host = Tirex::Config::get('sync_to_host');

if (! defined($host) || $host eq '') {
    die("Please set 'sync_to_host' config option in $config_file\n");
}

my @hosts = split(/[ ,]/, $host);

#-----------------------------------------------------------------------------
# daemonize and create pidfile
#-----------------------------------------------------------------------------
if (!$Tirex::DEBUG)
{
    chdir('/')                     or die("Cannot chdir to /: $!");
    open(STDIN,  '<', '/dev/null') or die("Cannot read /dev/null: $!");
    open(STDOUT, '>', '/dev/null') or die("Cannot write to /dev/null: $!");
    defined(my $pid = fork)        or die("Cannot fork: $!");
    exit(0) if $pid;
    setsid()                       or die("Cannot start a new session: $!");
    open(STDERR, '>&STDOUT')       or die("Cannot dup stdout: $!");
}

my $pidfile = Tirex::Config::get('syncd_pidfile', $Tirex::SYNCD_PIDFILE);
open(my $pidfh, '>', $pidfile) or syslog('err', "Can't open pidfile '$pidfile' for writing: $!\n");
print $pidfh "$$\n";
close($pidfh);

while (1)
{
    my $tiles = [];
    while (1)
    {
        my $msg = eval {
            Tirex::Message->new_from_socket($socket);
        };
        last unless $msg;
        syslog('debug', 'got request: %s', $msg->to_s()) if ($Tirex::DEBUG);
        my $metatile = $msg->to_metatile();
        my $filename = Tirex::Map->get_map_for_metatile($metatile)->get_tiledir() . '/' . $metatile->get_filename();
        $filename = $1 if ($filename =~ m!^/(.*)!);
        push(@$tiles, $filename);
    }

    if (!scalar(@$tiles))
    {
        syslog('debug', 'sleeping') if ($Tirex::DEBUG);
        sleep $delay;
        syslog('debug', 'waking up') if ($Tirex::DEBUG);
        next;
    }
    syslog('debug', 'acting on %d tiles', scalar(@$tiles)) if ($Tirex::DEBUG);

    my $names = join(" ", @$tiles);
    # fixme these could be run in parallel
    foreach my $host(@hosts)
    {
        my $command = $syncd_command;
        $command =~ s/%HOST%/$host/g;
        $command =~ s/%FILES%/$names/g;
        syslog('debug', "executing $command") if ($Tirex::DEBUG);
        system "$command" unless($dryrun);
	sleep 2;
        syslog('debug', "command done") if ($Tirex::DEBUG);
    }
}

__END__

=head1 NAME

tirex-syncd - sync rendered tiles to backup host

=head1 SYNOPSIS

tirex-syncd [OPTIONS]

=head1 OPTIONS

=over 8

=item B<-h>, B<--help>

Display help message.

=item B<-d>, B<--debug>

Run in debug mode.

=item B<-c>, B<--config=DIR>

Use the config directory DIR instead of /etc/tirex.

=item B<-n>, B<--dry-run>

Do not actually copy anything.

=back

=head1 DESCRIPTION

If you have several tile servers that you want to keep in sync (for instance as
backup when one fails), you can use the tirex-syncd. The syncd will be notified
by the master when a tile has been rendered and copy it to another server.

=head1 FILES

=over 8

=item F</etc/tirex/tirex.conf>

The configuration file.

=back

=head1 SEE ALSO

L<http://wiki.openstreetmap.org/wiki/Tirex>

=head1 AUTHORS

Frederik Ramm <frederik.ramm@geofabrik.de>, Jochen Topf
<jochen.topf@geofabrik.de> and possibly others.

=cut


#-- THE END ------------------------------------------------------------------
