summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/tpl/defs.pl2
-rw-r--r--data/tpl/redit3
-rw-r--r--data/tpl/vnedit8
-rw-r--r--data/tpl/vnpage24
-rw-r--r--data/tpl/vnpage_rel8
-rw-r--r--lib/ChangeLog14
-rw-r--r--lib/Multi/Anime.pm303
-rw-r--r--lib/Multi/Core.pm2
-rw-r--r--lib/Multi/Maintenance.pm43
-rw-r--r--lib/VNDB/Util/DB.pm36
-rw-r--r--lib/VNDB/Util/Tools.pm4
-rw-r--r--lib/VNDB/VN.pm23
-rw-r--r--lib/global.pl27
-rw-r--r--static/files/def.js28
-rw-r--r--static/files/style.css20
-rw-r--r--util/multi.pl2
-rw-r--r--util/updates/update_1.15.sql26
17 files changed, 525 insertions, 48 deletions
diff --git a/data/tpl/defs.pl b/data/tpl/defs.pl
index 6e568c11..c482478a 100644
--- a/data/tpl/defs.pl
+++ b/data/tpl/defs.pl
@@ -180,7 +180,7 @@ sub ttabs { # [vrp], obj, sel
sprintf('<a href="/%%s/hide"%s>%s</a>', $t eq 'v' ? ' id="vhide"' : '', $$o{hidden} ? 'unhide' : 'hide')
) : (),
!$$o{locked} || ($p{Authedit} && $p{Authlock}) ?
- ($s eq 'edit' ? 'edit' : '<a href="/%s/edit" '.($t eq 'v' || $t eq 'r' ? 'class="dropdown" rel="editDD"':'').'>edit</a>') : (),
+ ($s eq 'edit' ? 'edit' : '<a href="/%s/edit" '.($t eq 'v' || $t eq 'r' ? 'class="dropdown" rel="nofollow editDD"':'').'>edit</a>') : (),
$p{Authhist} ?
($s eq 'hist' ? 'history' : '<a href="/%s/hist">history</a>') : (),
diff --git a/data/tpl/redit b/data/tpl/redit
index 0e83b670..9567cce0 100644
--- a/data/tpl/redit
+++ b/data/tpl/redit
@@ -42,8 +42,7 @@
{ type => 'static', raw => 1, text => '<label>Platforms</label><ul class="platforms">'.join('', map { my $p = $_;
'<li><input type="checkbox" name="platforms" value="'.$_.'" id="'.$_.'" '.
(($d{form}{platforms} && grep { $p eq $_ } @{$d{form}{platforms}}) ? 'checked="checked" ':'').'/>'.
- '<acronym class="plat '.$_.'" title="'.$VNDB::PLAT->{$_}.'">'.$_.'</acronym>'.
- '<label for="'.$_.'">'.$VNDB::PLAT->{$_}.'</label></li>'
+ '<label for="'.$_.'"><acronym class="plat '.$_.'" title="'.$VNDB::PLAT->{$_}.'">&nbsp;</acronym>'.$VNDB::PLAT->{$_}.'</label></li>'
} sort { $VNDB::PLAT->{$a} cmp $VNDB::PLAT->{$b} } keys %$VNDB::PLAT).'</ul>' },
{ type => 'static', text => '<br />' },
diff --git a/data/tpl/vnedit b/data/tpl/vnedit
index dde98cb3..0e46ba47 100644
--- a/data/tpl/vnedit
+++ b/data/tpl/vnedit
@@ -47,6 +47,14 @@
{ type => 'input', name => '&nbsp;', short => 'l_vnn', pre => 'http://visual-novels.net/vn/index.php?option=com_content&amp;task=view&amp;id=', class => 'shortopts' },
{ type => 'input', name => '&nbsp;', short => 'l_cisv', pre => 'http://cisvisual.net/title/', class => 'shortopts' },
+ { type => 'static', text => '<br />' },
+ { type => 'input', name => 'Related anime', short => 'anime' },
+ { type => 'static', text => q|
+ Whitespace seperated list of <a href="http://anidb.net/">AniDB</a> anime IDs.
+ E.g. "1015 3348" will add <a href="http://anidb.net/a1015">Shingetsutan Tsukihime</a>
+ and <a href="http://anidb.net/a3348">Fate/stay night</a> as related anime.<br />
+ <b>Note:</b> It can take a few minutes for the anime titles to appear on the VN page.| },
+
{ type => 'sub', title => 'Categories', short => 'cat' },
{ type => 'hidden', short => 'categories' },
{ type => 'static', raw => 1, text => eval {
diff --git a/data/tpl/vnpage b/data/tpl/vnpage
index f602c1e2..1388fd03 100644
--- a/data/tpl/vnpage
+++ b/data/tpl/vnpage
@@ -20,6 +20,7 @@
[ l_wp => 'Wikipedia link', sub { $_[0] ? '<a href="http://en.wikipedia.org/wiki/'.$_[0].'">'.$_[0].'</a>' : 'No link' } ],
[ l_vnn => 'V-N.net link', sub { $_[0] ? '<a href="http://visual-novels.net/vn/index.php?option=com_content&amp;task=view&amp;id='.$_[0].'">'.$_[0].'</a>' : 'No link' } ],
[ l_cisv => 'CISVisual link', sub { $_[0] ? '<a href="http://cisvisual.net/title/'.$_[0].'">'.$_[0].'</a>' : 'No link' } ],
+ [ anime => 'Related anime', sub { join(' ', map qq|<a href="http://anidb.net/a$$_{id}">$$_{id}</a>|, sort { $a->{id} <=> $b->{id} } @{$_[0]}) } ],
[ categories => 'Categories', sub { join(' ', map { $VNDB::CAT->{substr($_->[0],0,1)}[1]{substr($_->[0],1,2)}.'('.$_->[1].')' } sort { $a->[0] cmp $b->[0] } @{$_[0]}) || 'No categories selected' }, 1 ],
[ relations => 'Relations', sub { join("<br />\n", map { $VNDB::VREL->[$_->{relation}].': '._hchar($_->{title}) } sort { $a->{id} <=> $b->{id} } @{$_[0]}) } ],
[ image => 'Image', sub { $_[0] > 0 ? sprintf '<img src="%s/cv/%02d/%d.jpg" />', $p{st}, $_[0]%100, $_[0] : $_[0] < 0 ? '[processing]' : 'No image'; } ],
@@ -78,14 +79,14 @@ if($d{vn}{length} || $d{vn}{alias} || @links) { ]]
<dt>Length</dt><dd>[[: $VNDB::VNLEN->[$d{vn}{length}][0] ]]- ([[: $VNDB::VNLEN->[$d{vn}{length}][1] ]])</dd>[[ } ]]-
[[ if($d{vn}{alias}) { ]]-
<dt>Aliases</dt><dd>[[: $d{vn}{alias} ]]</dd>[[ } ]]-
- [[ if(@links > 0) { ]]
+ [[ if(@links > 0) { ]]-
<dt>Links</dt><dd>[[= join(', ', map { '<a href="'.sprintf($_->[1],$_->[2]).'">'.$_->[0].'</a>' } @links) ]]</dd>[[ } ]]-
</dl>
[[ } ]]-
[[ if(@{$d{vn}{categories}}) { my %nolvl = (pli=>1,pbr=>1,gaa=>1,gab=>1); ]]-
<h3>Categories</h3>
- <dl class="vncat">
+ <dl>
[[ for (sort keys %$VNDB::CAT) {
my $c = $_;
my @c = map { my $s=$_;
@@ -110,6 +111,25 @@ if($d{vn}{length} || $d{vn}{alias} || @links) { ]]
</dl>
[[ } ]]-
+ [[ if(@{$d{vn}{anime}}) { ]]-
+ <h3>Related anime</h3>
+ <ul class="vnani">
+ [[ for (sort { $a->{year} < 1 ? 1 : $b->{year} < 1 ? -1 : $a->{year} <=> $b->{year} } @{$d{vn}{anime}}) { ]]-
+ <li>
+ -[[ if($_->{lastfetch} < 1) { ]]
+ <b>[[= $_->{lastfetch} < 0 ? '[unknown anidb id: ' : '[no information available at this time: ' ]]<a href="http://anidb.net/a[[= $_->{id} ]]">[[= $_->{id} ]]</a>]</b>
+ [[ } else {
+ my $l = join '-', qq|<a href="http://anidb.net/a$_->{id}" title="AniDB">DB</a>|,
+ $_->{nfo_id} ? qq|<a href="http://animenfo.com/animetitle,$_->{nfo_id},vndb.html" title="AnimeNFO">NFO</a>| : (),
+ $_->{ann_id} ? qq|<a href="http://www.animenewsnetwork.com/encyclopedia/anime.php?id=$_->{ann_id}" title="Anime News Network">ANN</a>| : ();
+ ]]
+ <b>[-[[= $l ]]-]</b> <acronym title="[[: $_->{title_kanji} ]]">[[: shorten $_->{title_romaji}, 40 ]]</acronym> <b>([[: $VNDB::ANITYPE->[$_->{type}][0] ]], -[[= $_->{year} ]])</b>
+ [[ } ]]
+ </li>
+ [[ } ]]
+ </ul>
+ [[ } ]]-
+
[[ if(@lang && grep { @{$_->{producers}} } @{$d{rel}}) { ]]-
<h3>Producers</h3>
<dl>
diff --git a/data/tpl/vnpage_rel b/data/tpl/vnpage_rel
index f2570548..f0edc35d 100644
--- a/data/tpl/vnpage_rel
+++ b/data/tpl/vnpage_rel
@@ -29,14 +29,10 @@
<tr>
<td class="tc1">[[= datestr($_->{released}) ]]</td>
<td class="tc2">[[= $_->{minage}<0 ? '' : $VNDB::VRAGES->{$_->{minage}} ]]</td>
- <td class="tc3">[[= join('', map { $_ ne 'oth' ? '<acronym class="plat '.$_.'" title="'._hchar($VNDB::PLAT->{$_}).'">'.$_.'</acronym>' : () } sort @{$_->{platforms}}) ]]</td>
+ <td class="tc3">[[= join('', map { $_ ne 'oth' ? '<acronym class="plat '.$_.'" title="'._hchar($VNDB::PLAT->{$_}).'">&nbsp;</acronym>' : () } sort @{$_->{platforms}}) ]]</td>
<td class="tc4"><acronym title="[[= $VNDB::RTYP->[$_->{type}] ]]- release">[[= lc substr($VNDB::RTYP->[$_->{type}],0,1) ]]</acronym></td>
<td class="tc5"><a href="/r[[= $_->{id} ]]" title="[[: $_->{original} || $_->{title} ]]">[[: shorten $_->{title},60 ]]</a></td>
-<!-- <td class="tc6">[[=
- join(', ',
- map {
- sprintf('<a href="/p%d" title="%s">%s</a>', $_->{id}, _hchar($_->{name}), _hchar(shorten $_->{name},20)) } @{$_->{producers}}) ]]</td>-->
- <td class="tc7">[[ if($_->{website}) { ]]<a href="[[: $_->{website} ]]"><acronym class="plat ext" title="WWW">www</acronym></a>[[ } ]]</td>
+ <td class="tc7">[[ if($_->{website}) { ]]<a href="[[: $_->{website} ]]" class="plat ext" title="WWW">&nbsp;</a>[[ } ]]</td>
</tr>
[[ } ]]-
[[ } ]]-
diff --git a/lib/ChangeLog b/lib/ChangeLog
index 2568880b..3e325ccd 100644
--- a/lib/ChangeLog
+++ b/lib/ChangeLog
@@ -5,7 +5,19 @@ TODO:
+ Remove all references to an item when it's hidden
(preferably with the option to re-add them when unhiding)
+ Add a link for the hidden 'h' option at /hist
- + The delete option should be removed
+ + The delete option should be removed
+ + AJAXified image upload system to avoid confusion about whether an image is
+ uploaded or not
+ + Use AniDB's daily title dump and implement a search feature to add
+ anime relations without having to visit AniDB.
+
+1.15 - ?
+ - Relation graph now also updated on VN title change
+ - Anime relations
+ - [js] Position of the dropdown box is now relative to the link element, not
+ the cursor
+ - Used inline-block instead of block for icon image sprites (to avoid stupid
+ float hacks)
1.14 - 2008-04-26
- Removed the ID gap prevention method
diff --git a/lib/Multi/Anime.pm b/lib/Multi/Anime.pm
new file mode 100644
index 00000000..44fafb92
--- /dev/null
+++ b/lib/Multi/Anime.pm
@@ -0,0 +1,303 @@
+
+#
+# Multi::Anime - Fetches anime info from AniDB
+#
+
+package Multi::Anime;
+
+use strict;
+use warnings;
+use POE 'Wheel::UDP';
+use Tie::ShareLite ':lock';
+use Socket 'inet_ntoa';
+use Time::HiRes 'time';
+
+
+sub TIMEOUT () { 100 } # not part of the API
+sub LOGIN_ACCEPTED () { 200 }
+sub LOGIN_ACCEPTED_NEW_VER () { 201 }
+sub ANIME () { 230 }
+sub NO_SUCH_ANIME () { 330 }
+sub NOT_LOGGED_IN () { 403 }
+sub LOGIN_FIRST () { 501 }
+sub CLIENT_BANNED () { 504 }
+sub INVALID_SESSION () { 506 }
+sub BANNED () { 555 }
+sub ANIDB_OUT_OF_SERVICE () { 601 }
+sub SERVER_BUSY () { 602 }
+
+my @expected_codes = ( TIMEOUT, LOGIN_ACCEPTED, LOGIN_ACCEPTED_NEW_VER, ANIME, NO_SUCH_ANIME, NOT_LOGGED_IN, LOGIN_FIRST, INVALID_SESSION );
+
+
+sub spawn {
+ # The 'anime' command doesn't actually do anything, it just
+ # adds IDs to process to the internal queue, which is seperate
+ # from the global processing queue.
+ # This module -only- fetches anime information in daemon mode!
+ # Calling the anime command with an ID as argument will force
+ # the information to be refreshed. This is not recommended,
+ # just use 'anime check' for normal usage.
+
+ my $p = shift;
+ POE::Session->create(
+ package_states => [
+ $p => [qw| _start shutdown cmd_anime nextcmd receivepacket updateanime |],
+ ],
+ heap => {
+ # POE::Wheels::UDP options
+ LocalAddr => '0.0.0.0',
+ LocalPort => 9000,
+ PeerAddr => do {
+ if(!$Multi::DAEMONIZE) {
+ my $a = gethostbyname('api.anidb.info');
+ die "ERROR: Couldn't resolve domain" if !defined $a;
+ inet_ntoa($a);
+ } else {
+ 0;
+ }
+ },
+ PeerPort => 9000,
+ # AniDB UDP API options
+ user => 'vnmulti',
+ pass => '',
+ client => 'multi',
+ clientver => 1,
+ # Misc settings
+ msgdelay => 10,
+ timeout => 30,
+ timeoutdelay => 0.4, # $delay = $msgdelay ^ (1 + $tm*$timeoutdelay)
+ maxtimeoutdelay => 2*3600, # two hours
+ cachetime => 30*24*3600, # one month
+
+ @_,
+ w => undef,
+ s => '', # session key, '' = not logged in
+ tm => 0, # number of repeated timeouts
+ lm => 0, # timestamp of last outgoing message, 0=no running msg
+ aid => 0, # anime ID of the last sent ANIME command
+ tag => int(rand()*50000),
+ },
+ );
+}
+
+
+sub _start {
+ $_[KERNEL]->alias_set('anime');
+ $_[KERNEL]->call(core => register => qr/^anime ([0-9]+|check)$/, 'cmd_anime');
+
+ # check for anime twice a day
+ $_[KERNEL]->post(core => addcron => '0 0,12 * * *', 'anime check');
+ $_[KERNEL]->sig('shutdown' => 'shutdown');
+
+ if(!$Multi::DAEMONIZE) {
+ # init the UDP 'connection'
+ $_[HEAP]{w} = POE::Wheel::UDP->new(
+ (map { $_ => $_[HEAP]{$_} } qw| LocalAddr LocalPort PeerAddr PeerPort |),
+ InputEvent => 'receivepacket',
+ Filter => POE::Filter::Stream->new(),
+ );
+
+ # start executing commands
+ $_[KERNEL]->delay(nextcmd => 0); #$_[HEAP]{msgdelay});
+ }
+}
+
+
+sub shutdown {
+ undef $_[HEAP]{w};
+ $_[KERNEL]->delay('nextcmd');
+ $_[KERNEL]->delay('receivepacket');
+}
+
+
+sub cmd_anime { # cmd, arg
+ my @push;
+ if($_[ARG1] eq 'check') {
+ # only animes we have never fetched, or haven't been updated for a month
+ my $q = $Multi::SQL->prepare(q|
+ SELECT id
+ FROM anime
+ WHERE lastfetch < ?
+ AND lastfetch <> -1|);
+ $q->execute(int(time-$_[HEAP]{cachetime}));
+ push @push, map $_->[0], @{$q->fetchall_arrayref([])};
+ $_[KERNEL]->call(core => log => 2, 'All anime info is up-to-date!') if !@push;
+ } else {
+ push @push, $_[ARG1];
+ }
+
+ if(@push) {
+ my $s = tie my %s, 'Tie::ShareLite', @VNDB::SHMOPTS;
+ $s->lock(LOCK_EX);
+ my @q = $s{anime} ? @{$s{anime}} : ();
+ push @q, grep {
+ my $ia = $_;
+ !(scalar grep $ia == $_, @q)
+ } @push;
+ $s{anime} = \@q;
+ $s->unlock();
+ }
+
+ $_[KERNEL]->post(core => finish => $_[ARG0]);
+}
+
+
+sub nextcmd {
+ return if $_[HEAP]{lm};
+
+ my $s = tie my %s, 'Tie::ShareLite', @VNDB::SHMOPTS;
+ my @q = $s{anime} ? @{$s{anime}} : ();
+ undef $s;
+
+ if(!@q) { # nothing to do...
+ $_[KERNEL]->delay(nextcmd => $_[HEAP]{msgdelay});
+ return;
+ }
+ my %cmd;
+
+ # not logged in, get a session
+ if(!$_[HEAP]{s}) {
+ %cmd = (
+ command => 'AUTH',
+ user => $_[HEAP]{user},
+ pass => $_[HEAP]{pass},
+ protover => 3,
+ client => $_[HEAP]{client},
+ clientver => $_[HEAP]{clientver},
+ enc => 'UTF-8',
+ );
+ $_[KERNEL]->call(core => log => 3, 'Authenticating with AniDB...');
+ }
+
+ # logged in, get anime
+ else {
+ $_[HEAP]{aid} = $q[0];
+ %cmd = (
+ command => 'ANIME',
+ aid => $q[0],
+ acode => 3973121, # aid, ANN id, NFO id, year, type, romaji, kanji
+ );
+ $_[KERNEL]->call(core => log => 3, 'Fetching info for a%d', $q[0]);
+ }
+
+ # send command
+ my $cmd = delete $cmd{command};
+ $cmd{tag} = ++$_[HEAP]{tag};
+ $cmd{s} = $_[HEAP]{s} if $_[HEAP]{s};
+ $cmd .= ' '.join('&', map {
+ $cmd{$_} =~ s/&/&amp;/g;
+ $cmd{$_} =~ s/\r?\n/<br \/>/g;
+ $_.'='.$cmd{$_}
+ } keys %cmd);
+ $_[HEAP]{w}->put({ payload => [ $cmd ]});
+ $VNDB::DEBUG && printf " > %s\n", $cmd;
+
+ $_[KERNEL]->delay(receivepacket => $_[HEAP]{timeout}, { payload => [ $_[HEAP]{tag}.' 100 TIMEOUT' ] });
+ $_[HEAP]{lm} = time;
+}
+
+
+sub receivepacket { # input, wheelid
+ $_[KERNEL]->delay('receivepacket'); # disable the timeout
+ my @r = split /\n/, $_[ARG0]{payload}[0];
+ my $delay = $_[HEAP]{msgdelay};
+
+ my($tag, $code, $msg) = ($1, $2, $3) if $r[0] =~ /^([0-9]+) ([0-9]+) (.+)$/;
+
+ if(!grep $_ == $code, @expected_codes) {
+ $_[KERNEL]->call(core => log => 1, "Received an unexpected reply after %.2fs!\n < %s",
+ time-$_[HEAP]{lm}, join("\n < ", @r));
+ } else {
+ $_[KERNEL]->call(core => log => 3, 'Received from AniDB after %.2fs: %d %s',
+ time-$_[HEAP]{lm}, $code, $msg);
+ $VNDB::DEBUG && print ' < '.join("\n < ", @r)."\n";
+ }
+
+ # just handle anime data, even if the tag is not correct
+ if($code == ANIME) {
+ $_[KERNEL]->yield(updateanime => $_[HEAP]{aid}, $r[1]);
+ }
+
+ # tag incorrect, ignore message
+ if($tag != $_[HEAP]{tag}) {
+ $_[KERNEL]->call(core => log => 3, 'Ignoring incorrect tag') if $code != ANIME;
+ return;
+ }
+
+ # try again later
+ if($code == TIMEOUT || $code == CLIENT_BANNED || $code == BANNED || $code == ANIDB_OUT_OF_SERVICE || $code == SERVER_BUSY) {
+ $_[HEAP]{tm}++;
+ $delay = $_[HEAP]{msgdelay}**(1 + $_[HEAP]{tm}*$_[HEAP]{timeoutdelay});
+ $delay = $_[HEAP]{maxtimeoutdelay} if $delay > $_[HEAP]{maxtimeoutdelay};
+ $_[KERNEL]->call(core => log => 1, 'Delaying %.0fs.', $delay);
+ }
+
+ # oops, wrong id
+ if($code == NO_SUCH_ANIME) {
+ $_[KERNEL]->yield(updateanime => $_[HEAP]{aid}, 'notfound');
+ }
+
+ # ok, we have a session now
+ if($code == LOGIN_ACCEPTED || $code == LOGIN_ACCEPTED_NEW_VER) {
+ $_[HEAP]{s} = $1 if $msg =~ /^\s*([a-zA-Z0-9]{4,8}) /;
+ }
+
+ # oops, we should've logged in, get a new session
+ if($code == NOT_LOGGED_IN || $code == LOGIN_FIRST || $code == INVALID_SESSION) {
+ $_[HEAP]{s} = '';
+ }
+
+ $_[HEAP]{lm} = $_[HEAP]{aid} = 0;
+ $_[HEAP]{tm} = 0 if $delay == $_[HEAP]{msgdelay};
+ $_[KERNEL]->delay(nextcmd => $delay);
+}
+
+
+sub updateanime { # aid, data|'notfound'
+ # aid, ANN id, NFO id, year, type, romaji, kanji, lastfetch
+ my @col = $_[ARG1] eq 'notfound'
+ ? ($_[ARG0], 0, 0, 0, 0, '', '', -1)
+ : (split(/\|/, $_[ARG1]), int time);
+
+ if($col[7] > 0) {
+ for (@col) {
+ $_ =~ s/<br \/>/\n/g;
+ $_ =~ s/`/'/g;
+ }
+ $col[3] = $1 if $col[3] =~ /^([0-9]+)/; # remove multi-year stuff
+ for(0..$#$VNDB::ANITYPE) {
+ $col[4] = $_ if lc($VNDB::ANITYPE->[$_][1]) eq lc($col[4]);
+ }
+ $col[4] = 0 if $col[4] !~ /^[0-9]+$/;
+ }
+
+ # try to UPDATE first
+ my $r = $Multi::SQL->do(q|
+ UPDATE anime
+ SET id = ?, ann_id = ?, nfo_id = ?, year = ?, type = ?,
+ title_romaji = ?, title_kanji = ?, lastfetch = ?
+ WHERE id = ?|,
+ undef, @col, $col[0]);
+
+ # fall back to INSERT when nothing was updated
+ $Multi::SQL->do(q|
+ INSERT INTO anime
+ (id, ann_id, nfo_id, year, type, title_romaji, title_kanji, lastfetch)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)|,
+ undef, @col) if $r < 1;
+
+ # remove from queue
+ my $s = tie my %s, 'Tie::ShareLite', @VNDB::SHMOPTS;
+ $s->lock(LOCK_EX);
+ my @q = grep $_ != $_[ARG0], ($s{anime} ? @{$s{anime}} : ());
+ $s{anime} = \@q;
+ $s->unlock();
+
+ $col[7] > 0
+ ? $_[KERNEL]->post(core => log => 2, 'Updated anime info for a%d', $col[0])
+ : $_[KERNEL]->post(core => log => 1, 'Anime a%d not found!', $col[0]);
+}
+
+
+1;
+
diff --git a/lib/Multi/Core.pm b/lib/Multi/Core.pm
index 6f94f860..2d9ea85f 100644
--- a/lib/Multi/Core.pm
+++ b/lib/Multi/Core.pm
@@ -123,7 +123,7 @@ sub log { # level, msg
close $F;
# (debug) log to stdout as well...
- #printf "[%s] %s\n", scalar localtime, $msg;
+ $VNDB::DEBUG && printf "[%s] %s\n", scalar localtime, $msg;
}
diff --git a/lib/Multi/Maintenance.pm b/lib/Multi/Maintenance.pm
index fb74e192..51fff936 100644
--- a/lib/Multi/Maintenance.pm
+++ b/lib/Multi/Maintenance.pm
@@ -11,10 +11,12 @@ use POE;
sub spawn {
+ # WARNING: these maintenance tasks can block the process for a few seconds
+
my $p = shift;
POE::Session->create(
package_states => [
- $p => [qw| _start cmd_maintenance vncache ratings prevcache integrity |],
+ $p => [qw| _start cmd_maintenance vncache ratings prevcache integrity unkanime |],
],
);
}
@@ -22,7 +24,7 @@ sub spawn {
sub _start {
$_[KERNEL]->alias_set('maintenance');
- $_[KERNEL]->call(core => register => qr/^maintenance((?: (?:all|vncache|ratings|prevcache|integrity))+)$/, 'cmd_maintenance');
+ $_[KERNEL]->call(core => register => qr/^maintenance((?: (?:all|vncache|ratings|prevcache|integrity|unkanime))+)$/, 'cmd_maintenance');
# Perform all maintenance functions every day on 0:00
$_[KERNEL]->post(core => addcron => '0 0 * * *', 'maintenance all');
@@ -32,10 +34,11 @@ sub _start {
sub cmd_maintenance {
local $_ = $_[ARG1];
- $_[KERNEL]->yield('vncache') if /(vncache|all)/;
- $_[KERNEL]->yield('ratings') if /(ratings|all)/;
- $_[KERNEL]->yield('prevcache') if /(prevcache|all)/;
- $_[KERNEL]->yield('integrity') if /(integrity|all)/;
+ $_[KERNEL]->yield('vncache') if /(?:vncache|all)/;
+ $_[KERNEL]->yield('ratings') if /(?:ratings|all)/;
+ $_[KERNEL]->yield('prevcache') if /(?:prevcache|all)/;
+ $_[KERNEL]->yield('integrity') if /(?:integrity|all)/;
+ $_[KERNEL]->yield('unkanime') if /(?:unkanime|all)/;
$_[KERNEL]->post(core => finish => $_[ARG0]);
}
@@ -55,6 +58,7 @@ sub ratings {
sub prevcache {
$_[KERNEL]->call(core => log => 3 => 'Updating prev column in the changes table...');
+ # this can take a while, maybe split these up in 3 queries?
$Multi::SQL->do(q|SELECT update_prev('vn', ''), update_prev('releases', ''), update_prev('producers', '')|);
}
@@ -79,6 +83,33 @@ sub integrity {
}
+sub unkanime {
+ # warn for VNs with a non-existing anidb id
+ # (maybe do an automated edit or something in the future)
+
+ my $q = $Multi::SQL->prepare(q|
+ SELECT v.id, va.aid
+ FROM vn_anime va
+ JOIN vn v ON va.vid = v.latest
+ JOIN anime a ON va.aid = a.id
+ WHERE a.lastfetch < 0|);
+ $q->execute();
+ my $r = $q->fetchall_arrayref([]);
+ my %aid = map {
+ my $a=$_;
+ $a->[1] => join(',', map { $a->[1] == $_->[1] ? $_->[0] : () } @$r)
+ } @$r;
+
+ if(keys %aid) {
+ $_[KERNEL]->call(core => log => 1, '!NON-EXISTING RELATED ANIME FOUND!: %s',
+ join('; ', map { 'a'.$_.':v'.$aid{$_} } keys %aid)
+ );
+ } else {
+ $_[KERNEL]->call(core => log => 3, 'No problems found with the related anime');
+ }
+}
+
+
1;
diff --git a/lib/VNDB/Util/DB.pm b/lib/VNDB/Util/DB.pm
index 3f99aff8..f10f7b67 100644
--- a/lib/VNDB/Util/DB.pm
+++ b/lib/VNDB/Util/DB.pm
@@ -630,10 +630,11 @@ sub DBGetVN { # %options->{ id rev char search order results page what cati cate
);
$_->{c_released} = sprintf '%08d', $_->{c_released} for @$r;
- if($o{what} =~ /(relations|categories)/ && $#$r >= 0) {
+ if($o{what} =~ /(?:relations|categories|anime)/ && $#$r >= 0) {
my %r = map {
$r->[$_]{relations} = [];
$r->[$_]{categories} = [];
+ $r->[$_]{anime} = [];
($r->[$_]{cid}, $_)
} 0..$#$r;
@@ -646,6 +647,16 @@ sub DBGetVN { # %options->{ id rev char search order results page what cati cate
)});
}
+ if($o{what} =~ /anime/) {
+ push(@{$r->[$r{$_->{vid}}]{anime}}, $_) && delete $_->{vid} for (@{$s->DBAll(q|
+ SELECT va.vid, a.*
+ FROM vn_anime va
+ JOIN anime a ON va.aid = a.id
+ WHERE va.vid IN(!l)|,
+ [ keys %r ]
+ )});
+ }
+
if($o{what} =~ /relations/) {
my $rel = $s->DBAll(q|
SELECT rel.vid1, rel.vid2, rel.relation, vr.title
@@ -669,7 +680,7 @@ sub DBGetVN { # %options->{ id rev char search order results page what cati cate
}
-sub DBAddVN { # %options->{ columns in vn_rev + comm + relations }
+sub DBAddVN { # %options->{ columns in vn_rev + comm + relations + categories + anime }
my($s, %o) = @_;
$s->DBExec(q|
@@ -690,7 +701,7 @@ sub DBAddVN { # %options->{ columns in vn_rev + comm + relations }
}
-sub DBEditVN { # id, %options->( columns in vn_rev + comm + relations + categories + uid + causedby }
+sub DBEditVN { # id, %options->( columns in vn_rev + comm + relations + categories + anime + uid + causedby }
my($s, $vid, %o) = @_;
$s->DBExec(q|
@@ -733,6 +744,25 @@ sub _insert_vn_rev {
VALUES (%d, %d, %d)|,
$cid, $_->[1], $_->[0]
) for (@{$o->{relations}});
+
+ if(@{$o->{anime}}) {
+ $s->DBExec(q|
+ INSERT INTO vn_anime (vid, aid)
+ VALUES (%d, %d)|,
+ $cid, $_
+ ) for (@{$o->{anime}});
+
+ # insert unknown anime
+ my $a = $s->DBAll(q|
+ SELECT id FROM anime WHERE id IN(!l)|,
+ $o->{anime});
+ $s->DBExec(q|
+ INSERT INTO anime (id) VALUES (%d)|, $_
+ ) for (grep {
+ my $ia = $_;
+ !(scalar grep $ia == $_->{id}, @$a)
+ } @{$o->{anime}});
+ }
}
diff --git a/lib/VNDB/Util/Tools.pm b/lib/VNDB/Util/Tools.pm
index c2d9268b..f338ad7c 100644
--- a/lib/VNDB/Util/Tools.pm
+++ b/lib/VNDB/Util/Tools.pm
@@ -132,8 +132,8 @@ sub AddDefaultStuff {
sub RunCmd { # cmd
my $s = tie my %s, 'Tie::ShareLite', @VNDB::SHMOPTS;
$s->lock(LOCK_EX);
- $s{queue} = [] if !$s{queue};
- push(@{$s{queue}}, grep !/^-/, $_[1]);
+ my @q = ( ($s{queue} ? @{$s{queue}} : ()), $_[1] );
+ $s{queue} = \@q;
$s->unlock();
}
diff --git a/lib/VNDB/VN.pm b/lib/VNDB/VN.pm
index ea13445b..4732e278 100644
--- a/lib/VNDB/VN.pm
+++ b/lib/VNDB/VN.pm
@@ -24,13 +24,13 @@ sub VNPage {
my $v = $self->DBGetVN(
id => $id,
- what => 'extended relations categories'.($r->{rev} ? ' changes' : ''),
+ what => 'extended relations categories anime'.($r->{rev} ? ' changes' : ''),
$r->{rev} ? ( rev => $r->{rev} ) : ()
)->[0];
return $self->ResNotFound if !$v->{id};
$r->{diff} ||= $v->{prev} if $r->{rev};
- my $c = $r->{diff} && $self->DBGetVN(id => $id, rev => $r->{diff}, what => 'extended changes relations categories')->[0];
+ my $c = $r->{diff} && $self->DBGetVN(id => $id, rev => $r->{diff}, what => 'extended changes relations categories anime')->[0];
$v->{next} = $self->DBGetHist(type => 'v', id => $id, next => $v->{cid}, showhid => 1)->[0]{id} if $r->{rev};
if($page eq 'rg' && $v->{rgraph}) {
@@ -67,7 +67,7 @@ sub VNEdit {
my $rev = $self->FormCheck({ name => 'rev', required => 0, default => 0, template => 'int' })->{rev};
- my $v = $self->DBGetVN(id => $id, what => 'extended changes relations categories', $rev ? ( rev => $rev ) : ())->[0] if $id;
+ my $v = $self->DBGetVN(id => $id, what => 'extended changes relations categories anime', $rev ? ( rev => $rev ) : ())->[0] if $id;
return $self->ResNotFound() if $id && !$v;
return $self->ResDenied if !$self->AuthCan('edit') || ($v->{locked} && !$self->AuthCan('lock'));
@@ -76,6 +76,7 @@ sub VNEdit {
( map { $_ => $v->{$_} } qw| title desc alias img_nsfw length l_wp l_cisv l_vnn | ),
relations => join('|||', map { $_->{relation}.','.$_->{id}.','.$_->{title} } @{$v->{relations}}),
categories => join(',', map { $_->[0].$_->[1] } sort { $a->[0] cmp $b->[0] } @{$v->{categories}}),
+ anime => join(' ', sort { $a <=> $b } map $_->{id}, @{$v->{anime}}),
) : ();
my $frm = {};
@@ -88,18 +89,21 @@ sub VNEdit {
{ name => 'l_wp', required => 0, default => '', maxlength => 150 },
{ name => 'l_cisv', required => 0, default => 0, template => 'int' },
{ name => 'l_vnn', required => 0, default => 0, template => 'int' },
+ { name => 'anime', required => 0, default => '' },
{ name => 'img_nsfw', required => 0 },
{ name => 'categories', required => 0, default => '' },
{ name => 'relations', required => 0, default => 0 },
{ name => 'comm', required => 0, default => '' },
);
$frm->{img_nsfw} = $frm->{img_nsfw} ? 1 : 0;
+ $frm->{anime} = join(' ', sort { $a <=> $b } grep /^[0-9]+$/, split(/\s+/, $frm->{anime})); # re-sort
return $self->ResRedirect('/v'.$id, 'post')
- if $id && !$self->ReqParam('img') && 10 == scalar grep { $b4{$_} eq $frm->{$_} } keys %b4;
+ if $id && !$self->ReqParam('img') && 11 == scalar grep { $b4{$_} eq $frm->{$_} } keys %b4;
my $relations = [ map { /^([0-9]+),([0-9]+)/ && $2 != $id ? ( [ $1, $2 ] ) : () } split /\|\|\|/, $frm->{relations} ];
my $cat = [ map { [ substr($_,0,3), substr($_,3,1) ] } split /,/, $frm->{categories} ];
+ my $anime = [ split / /, $frm->{anime} ];
# upload image
my $imgid = '';
@@ -134,6 +138,7 @@ sub VNEdit {
my %args = (
( map { $_ => $frm->{$_} } qw| title desc alias comm length l_wp l_cisv l_vnn img_nsfw| ),
image => $imgid,
+ anime => $anime,
relations => $relations,
categories => $cat,
);
@@ -149,6 +154,13 @@ sub VNEdit {
my %new = map { $_->[1] => $_->[0] } @$relations;
$self->VNUpdReverse(\%old, \%new, $id, $cid);
}
+ # also regenerate relation graph if the title changes
+ elsif($frm->{title} ne $b4{title}) {
+ $self->RunCmd('relraph '.$id);
+ }
+
+ # check for new anime data
+ $self->RunCmd('anime check') if $frm->{anime} ne $b4{anime};
return $self->ResRedirect('/v'.$id.'?rev='.$cid, 'post');
}
@@ -302,7 +314,7 @@ sub VNUpdReverse { # old, new, id, cid
}
for my $i (keys %upd) {
- my $r = $self->DBGetVN(id => $i, what => 'extended relations categories')->[0];
+ my $r = $self->DBGetVN(id => $i, what => 'extended relations categories anime')->[0];
my @newrel;
$_->{id} != $id && push @newrel, [ $_->{relation}, $_->{id} ]
for (@{$r->{relations}});
@@ -312,6 +324,7 @@ sub VNUpdReverse { # old, new, id, cid
comm => 'Reverse relation update caused by revision '.$cid.' of v'.$id,
causedby => $cid,
uid => 1, # Multi - hardcoded
+ anime => [ map $_->{id}, @{$r->{anime}} ],
( map { $_ => $r->{$_} } qw| title desc alias categories img_nsfw length l_wp l_cisv l_vnn image | )
);
}
diff --git a/lib/global.pl b/lib/global.pl
index 74c3c070..890046b2 100644
--- a/lib/global.pl
+++ b/lib/global.pl
@@ -10,7 +10,7 @@ our $PLAT = {
lin => 'Linux',
mac => 'Mac OS',
dvd => 'DVD Player',
- gba => 'Game Boy Advanced',
+ gba => 'Game Boy Advance',
nds => 'Nintendo DS',
psp => 'Playstation Portable',
ps => 'Playstation',
@@ -21,6 +21,7 @@ our $PLAT = {
oth => 'Other'
};
+
# NOTE: don't forget to update dyna.js
our $MED = {
cd => 'CD',
@@ -32,18 +33,21 @@ our $MED = {
otc => 'Other (console)',
};
+
our $PROT = {
co => 'Company',
in => 'Individual',
ng => 'Amateur group',
};
+
our $RTYP = [
'Complete',
'Partial',
'Trial'
];
+
# Yes, this is the category list. No, changing something here may
# not change it on the entire site - many things are still hardcoded
our $CAT = {
@@ -104,6 +108,7 @@ our $LSTAT = [
'Other', # XXX: hardcoded at 6
];
+
our $VREL = [
'Sequel',
'Prequel', # 1
@@ -146,6 +151,26 @@ our $VRAGES = {
};
+our $ANITYPE = [
+ # VNDB AniDB
+ [ 'unknown', 'unknown', ],
+ [ 'TV', 'TV Series' ],
+ [ 'OVA', 'OVA' ],
+ [ 'Movie', 'Movie' ],
+ [ 'unknown', 'Other' ],
+ [ 'unknown', 'Web' ],
+ [ 'TV Special', 'TV Special' ],
+ [ 'unknown', 'Music Video' ],
+];
+# AniDB defines:
+# id="1", name="unknown
+# id="2", name="TV Series
+# id="3", name="OVA
+# id="4", name="Movie
+# id="5", name="Other
+# id="6", name="Web
+# id="7", name="TV Special
+# id="8", name="Music Video
diff --git a/static/files/def.js b/static/files/def.js
index fcb98421..72b5cd80 100644
--- a/static/files/def.js
+++ b/static/files/def.js
@@ -79,31 +79,41 @@ function formtoggle(n) {
var ddx;var ddy;var dds=null;
function dropDown(e) {
e = e || window.event;
- var tg = e.target || e.srcElement; // get target element
+ var tg = e.target || e.srcElement;
if(tg.nodeType == 3)
tg = tg.parentNode;
+
if(!dds && (tg.nodeName.toLowerCase() != 'a' || !tg.rel || tg.className.indexOf('dropdown') < 0))
return;
- var mouseX = e.pageX || (e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft);
- var mouseY = e.pageY || (e.clientY + document.body.scrollTop + document.documentElement.scrollTop);
+ if(tg.rel)
+ tg.rel = tg.rel.replace(/ *nofollow */,"");
+ if(!dds && !tg.rel)
+ return;
+
if(!dds) {
- var obj = x(tg.rel);
- ddx = mouseX-20;
- ddy = mouseY+10;
+ var obj=tg;
+ ddx = ddy = 0;
+ do {
+ ddx += obj.offsetLeft;
+ ddy += obj.offsetTop;
+ } while(obj = obj.offsetParent);
+ ddy += 16;
+ obj = x(tg.rel);
obj.style.left = ddx+'px';
obj.style.top = ddy+'px';
dds = tg;
}
+
if(dds) {
+ var mouseX = e.pageX || (e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft);
+ var mouseY = e.pageY || (e.clientY + document.body.scrollTop + document.documentElement.scrollTop);
var obj = x(dds.rel);
- if((mouseX < ddx || mouseX > ddx+obj.offsetWidth || mouseY < ddy-20 || mouseY > ddy + obj.offsetHeight)
+ if((mouseX < ddx-5 || mouseX > ddx+obj.offsetWidth+5 || mouseY < ddy-20 || mouseY > ddy + obj.offsetHeight)
|| (mouseY < ddy && tg.nodeName.toLowerCase() == 'a' && tg != dds)) {
obj.style.left = '-500px';
dds = null;
}
- return;
}
- return true;
}
diff --git a/static/files/style.css b/static/files/style.css
index 5f78116e..6a8ab4fb 100644
--- a/static/files/style.css
+++ b/static/files/style.css
@@ -377,6 +377,9 @@ dd {
dl.vnrel dd, dl.vncat dd {
padding: 0 0 0 75px;
}
+ul.vnani i { color: #999 }
+ul.vnani b { color: #999; font-weight: normal; font-size: 10px; }
+ul.vnani acronym { border: 0 }
ul {
padding: 0 0 0 17px;
}
@@ -419,9 +422,11 @@ img.right {
float: left;
margin: 0 0 0 15px;
}
-#vnheader dl {
+#vnheader dl, #vnheader ul {
margin: 0 0 0 270px;
+ padding: 0;
}
+#vnheader li { list-style-type: none; margin: 0; padding: 0; }
#vnheader h3 {
margin: 5px 0 0 260px;
font-size: 13px;
@@ -642,16 +647,13 @@ thead tr td a { text-decoration: none; }
background: url(/files/platforms.png) no-repeat;
width: 16px;
height: 14px;
- margin: 0 2px 0 0;
+ margin: 0 5px 0 0;
overflow: hidden;
- float: right;
- text-indent: -999px;
- display: block;
+ display:-moz-inline-stack;
+ display: inline-block;
padding: 0;
border: 0;
-}
-li .plat {
- float: left;
+ text-decoration: none;
}
.plat.oth { background: none; }
.plat.dc { background-position: 0px 0px; }
@@ -709,7 +711,7 @@ b.diff_del { font-weight: normal; background-color: #fcc; }
#tre .tc1 { width: 75px; padding-left: 10px; }
#tre .tc2 { width: 60px; text-align: center; }
-#tre .tc3 { width: 55px; margin: 0; padding: 0; white-space: nowrap; }
+#tre .tc3 { width: 55px; margin: 0; padding: 0; white-space: nowrap; text-align: right }
#tre .tc4 { width: 10px; text-align: right; padding-right:3px; }
#tre .tc7 { width: 16px; margin: 0; padding: 0; white-space: nowrap; }
diff --git a/util/multi.pl b/util/multi.pl
index 022160ee..3e6e07a7 100644
--- a/util/multi.pl
+++ b/util/multi.pl
@@ -44,6 +44,7 @@ use Multi::Core;
use Multi::RG;
use Multi::Image;
use Multi::Sitemap;
+use Multi::Anime;
use Multi::Maintenance;
use Multi::IRC;
@@ -77,6 +78,7 @@ Multi::Core->spawn();
Multi::RG->spawn();
Multi::Image->spawn();
Multi::Sitemap->spawn();
+Multi::Anime->spawn();
Multi::Maintenance->spawn();
Multi::IRC->spawn() if !$VNDB::DEBUG;
diff --git a/util/updates/update_1.15.sql b/util/updates/update_1.15.sql
new file mode 100644
index 00000000..2f2f814c
--- /dev/null
+++ b/util/updates/update_1.15.sql
@@ -0,0 +1,26 @@
+
+
+-- remove the old image hashes
+ALTER TABLE vn_rev DROP COLUMN image_old;
+
+
+
+-- Add anime relations
+CREATE TABLE anime (
+ id integer NOT NULL PRIMARY KEY, -- anidb id
+ year smallint NOT NULL DEFAULT 0,
+ ann_id integer NOT NULL DEFAULT 0,
+ nfo_id varchar(200) NOT NULL DEFAULT '',
+ type smallint NOT NULL DEFAULT 0, -- TV/OVA/etc (global.pl)
+ title_romaji varchar(250) NOT NULL DEFAULT '',
+ title_kanji varchar(250) NOT NULL DEFAULT '',
+ lastfetch bigint NOT NULL DEFAULT 0 -- -1:not found, 0: not fetched, >0: timestamp
+) WITHOUT oids;
+
+CREATE TABLE vn_anime (
+ vid integer NOT NULL REFERENCES vn_rev (id) DEFERRABLE INITIALLY DEFERRED,
+ aid integer NOT NULL REFERENCES anime (id) DEFERRABLE INITIALLY DEFERRED,
+ PRIMARY KEY(vid, aid)
+) WITHOUT oids;
+
+