#!/v/openpkg/sw/bin/perl

use IO::File;
use IO::All;
use File::Find;
use OSSP::uuid;
use Date::Format;
use Date::Parse;
use DBI;
use DBD::SQLite;
use DBIx::Simple;

#   prepare UUID generation
my $uuid_ns_url = new OSSP::uuid;
my $uuid_ns     = new OSSP::uuid;
my $uuid        = new OSSP::uuid;
$uuid_ns_url->load("ns:URL");
$uuid_ns->make("v3", $uuid_ns_url, "http://www.ossp.org/");

#   prepare content generation
my $rss_refs  = '';
my $rss_items = '';
my $atom_items = '';

#   read FTP file information
my $ftp_db      = "ftp.db";
my $ftp_txt     = "ftp.txt";
my $ftp_rooturl = "ftp://ftp.openpkg.org";
my $ftp_rootdir = "/v/openpkg/ftp";
my @ftp_subdirs = ("$ftp_rootdir/current",
                   "$ftp_rootdir/release/*/UPD",
                   "$ftp_rootdir/contrib"
                   );

#   load old state
print STDERR "++ load old state\n";
my $db_init = (-f $ftp_db ? 0 : 1);
my $db = DBIx::Simple->connect(
    "dbi:SQLite:dbname=$ftp_db", "", "",
    { RaiseError => 0, AutoCommit => 0 }
);
if ($db_init) {
    $db->query(q{
        CREATE TABLE fs (
            file    TEXT PRIMARY KEY,
            mtime   INTEGER,
            age     INTEGER,
            found   INTEGER
        );
    }) or die $db->error();
    $db->query(q{
        CREATE TABLE log (
            date    TEXT,
            path    TEXT,
            file    TEXT,
            msg     TEXT
        );
    }) or die $db->error();
}

#   determine new state
print STDERR "++ determine new state\n";
$db->query(q{ UPDATE fs SET found = 0; });
my @dirs = ();
foreach my $dir (@ftp_subdirs) {
    push(@dirs, glob($dir));
}
find({
    bydepth  => 1,
    follow   => 0,
    no_chdir => 1,
    wanted   => sub {
        my $f = $_;
        if ($f =~ m/^\Q$ftp_rootdir\/\E(.+?-[^-]+-[^-]+\.(?:src|[^-]+-[^-]+-[^-]+)\.(?:rpm|sh))$/ and $f !~ m|/00UPLOAD/|) {
            my $key = $1;
            my ($exists) = $db->query(q{ SELECT 1 FROM fs WHERE file = ?; }, $key)->list;
            if ($exists) {
                $db->query(q{
                    UPDATE fs SET found = 1, age = age + 1 WHERE file = ?;
                }, $key) or die $db->error();
                my ($age) = $db->query(q{ SELECT age FROM fs WHERE file = ?; }, $key)->list;
                next if ($age < 10);
            }
            else {
                $db->query(q{
                    INSERT INTO fs VALUES (?, 0, 0, 1);
                }, $key) or die $db->error();
            }
            my @stat = stat($f);
            $db->query(q{
                UPDATE fs SET mtime = ? WHERE file = ?;
            }, $stat[9], $key) or die $db->error();
        }
    }
}, @dirs);
$db->commit();

#   determine difference (pass 1)
print STDERR "++ determine difference (pass 1)\n";
my $add = {};
my $del = {};
foreach my $row ($db->query(q{ SELECT * FROM fs WHERE found = 0; })->hashes()) {
    my ($path, $file, $name, $version, $release, $type) =
        ($row->{"file"} =~ m/^(.+?\/)(([^\/]+?)-([^-]+)-([^-]+)\.(src|[^-]+-[^-]+-[^-]+)\.(?:rpm|sh))$/);
    $del->{"$path$name"} = $row->{"file"};
    $del->{"$path$name-$version"} = $row->{"file"};
    $del->{"$path$name-$version-$release"} = $row->{"file"};
}
foreach my $row ($db->query(q{ SELECT * FROM fs WHERE found = 1 AND age = 0; })->hashes()) {
    my ($path, $file, $name, $version, $release, $type) =
        ($row->{"file"} =~ m/^(.+?\/)(([^\/]+?)-([^-]+)-([^-]+)\.(src|[^-]+-[^-]+-[^-]+)\.(?:rpm|sh))$/);
    $add->{"$path$name"} = $row->{"file"};
    $add->{"$path$name-$version"} = $row->{"file"};
    $add->{"$path$name-$version-$release"} = $row->{"file"};
}

#   determine difference (pass 2)
print STDERR "++ determine difference (pass 2)\n";
my @diff = ();
my $i = 0;
foreach my $row ($db->query(q{ SELECT * FROM fs WHERE found = 0; })->hashes()) {
    my ($path, $file, $name, $version, $release, $type) =
        ($row->{"file"} =~ m/^(.+?\/)(([^\/]+?)-([^-]+)-([^-]+)\.(src|[^-]+-[^-]+-[^-]+)\.(?:rpm|sh))$/);
    if (not defined($add->{"$path$name-$version"}) &&
        not defined($add->{"$path$name"})) {
        my $date = time2str("%Y-%m-%d %H:%M:%S", time()-((14*60)+$i++));
        my $msg = "removed package";
        $db->query(q{
            INSERT INTO log VALUES (?,?,?,?);
        }, $date, $path, $file, $msg) or die $db->error();
    }
}
foreach my $row ($db->query(q{ SELECT * FROM fs WHERE found = 1 AND age = 0; })->hashes()) {
    my ($path, $file, $name, $version, $release, $type) =
        ($row->{"file"} =~ m/^(.+?\/)(([^\/]+?)-([^-]+)-([^-]+)\.(src|[^-]+-[^-]+-[^-]+)\.(?:rpm|sh))$/);
    my $date = time2str("%Y-%m-%d %H:%M:%S", $row->{"mtime"});
    my $msg = ($db_init ? "existing" : "added");
    if (defined($del->{"$path$name-$version"})) {
        $msg .= " update";
    }
    elsif (defined($del->{"$path$name"})) {
        $msg .= " upgrade";
    }
    else {
        $msg .= ($db_init ? "" :" new");
    }
    $msg .= " package";
    $db->query(q{
        INSERT INTO log (date, path, file, msg) VALUES (??);
    }, $date, $path, $file, $msg) or die $db->error();
}

#   cleanup
print STDERR "++ cleanup\n";
$db->query(q{ DELETE FROM fs WHERE found = 0; });
$db->query(q{ UPDATE fs SET found = 0; });
$db->commit();

#  information parsing and content generation
print STDERR "++ generate feed content\n";
foreach my $row ($db->query(q{ SELECT * FROM log ORDER BY date DESC LIMIT 100; })->hashes()) {
    #   determine URL
    my $link = sprintf("%s/%s", $ftp_rooturl, $row->{"path"});
    my $action = "";
    $action = $1 if ($row->{"msg"} =~ m/(added|update|upgrade|removed)/);
    my $link0 = sprintf("%s/%s%s#%s", $ftp_rooturl, $row->{"path"}, $row->{"file"}, $action);

    #   determine title and description
    my ($date, $time) = ($row->{"date"} =~ m|^(.+)\s+(.+)$|);
    my $title = sprintf("%s: %s: %s%s", $date, $row->{"msg"}, $row->{"path"}, $row->{"file"});
    my $desc = $title;

    #   generate RSS/1.0 entry
    $rss_refs .=
        "<rdf:li resource=\"$link0\"/>\n"; 
    $rss_items .=
        "<item rdf:about=\"$link0\">\n" .
        "  <link>$link</link>\n" .
        "  <title>$title</title>\n" .
        "  <dc:date>$date</dc:date>\n" .
        "  <description>$desc</description>\n" .
        "</item>\n";

    #   generate Atom/1.0 entry
    $uuid->make("v3", $uuid_ns, sprintf("%s %s/%s", $row->{"msg"}, $row->{"path"}, $row->{"file"}));
    $atom_items .=
        "<entry>\n" .
        "  <id>urn:uuid:".$uuid->export("str")."</id>\n" .
        "  <link rel=\"alternate\" href=\"$link\"/>\n" .
        "  <title type=\"html\">$title</title>\n" .
        sprintf("  <updated>%sT%sZ</updated>\n", $date, $time) .
        "  <content type=\"html\">$desc</content>\n" .
        "</entry>\n";
}

#   generate RSS/1.0 output
$rss_refs =~ s/^/      /mg;
$rss_items =~ s/^/  /mg;
my $rdf =
    "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" .
    "<!-- OpenPKG Content Syndication News Feed -->\n" .
    "<!-- Syntax: RSS/1.0 RDF/1999-02-22 XML/1.0 -->\n" .
    "<rdf:RDF\n" .
    "  xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n" .
    "  xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n" .
    "  xmlns=\"http://purl.org/rss/1.0/\">\n" .
    "  <channel rdf:about=\"http://www.openpkg.org/news/ftp.rss.xml\">\n" .
    "    <link>ftp://ftp.openpkg.org/</link>\n" .
    "    <title>OpenPKG Download Service</title>\n" .
    "    <description>List of all OpenPKG Download Service file changes</description>\n" .
    "    <image rdf:resource=\"http://www.openpkg.org/favicon.ico\"/>\n" .
    "    <items>\n" .
    "      <rdf:Seq>\n" .
    $rss_refs .
    "      </rdf:Seq>\n" .
    "    </items>\n" .
    "  </channel>\n" .
    $rss_items .
    "</rdf:RDF>\n";

#   generate Atom/1.0 output
$atom_items =~ s/^/  /mg;
$date = time2str("%Y-%m-%dT%H:%M:%SZ", time());
my $atom =
    "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" .
    "<!-- OpenPKG Content Syndication News Feed -->\n" .
    "<!-- Syntax: Atom/1.0 XML/1.0 -->\n" .
    "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" .
    "  <id>http://www.openpkg.org/news/ftp.atom.xml</id>\n" .
    "  <link rel=\"self\" href=\"http://www.openpkg.org/news/ftp.atom.xml\"/>\n" .
    "  <link rel=\"alternate\" href=\"ftp://ftp.openpkg.org/\"/>\n" .
    "  <title>OpenPKG Download Service</title>\n" .
    "  <subtitle>List of all OpenPKG Download Service file changes</subtitle>\n" .
    "  <icon>http://www.openpkg.org/favicon.ico</icon>\n" .
    "  <updated>$date</updated>\n" .
    "  <author>\n" .
    "    <name>OpenPKG Project</name>\n" .
    "    <email>openpkg\@openpkg.org</email>\n" .
    "    <uri>http://www.openpkg.org</uri>\n" .
    "  </author>\n" .
    $atom_items .
    "</feed>\n";

#   store RSS/1.0 output
print STDERR "++ store RSS feed content\n";
my $io = new IO::File ">ftp.rss.xml" or die;
$io->print($rdf);
$io->close();

#   store Atom/1.0 output
print STDERR "++ store Atom feed content\n";
my $io = new IO::File ">ftp.atom.xml" or die;
$io->print($atom);
$io->close();

#   close database
$db->disconnect();

